No major UI toolkit is thread safe.
This means that these toolkits are not designed to have their exposed methods be invoked by multiple threads at the same time from multiple threads. The main reason is that building thread safe toolkits is both a very hard problem and can have very complicated semantics for the consumer of the toolkit.
Developers typically use multiple threads in UI applications to offload tasks that would otherwise block the user interface. The work is offloaded to a background thread that can take as long as it wants or can perform various blocking operations like disk or network operations without affecting the interactive nature of the application.
When the background code completes its work, it queues an operation to be executed on the main thread to perform any required UI updates.
In MonoTouch and MonoMac the queuing of the operation from the background thread to the main thread is done using the InvokeOnMainThread or BeginInvokeOnMainThread methods.
The rule among toolkits is: do not access any toolkit APIs from the background thread since there is nothing in the toolkit API to defend against internal state corruption caused by multiple threads updating internals at the same time. Failure to follow this rule can lead to subtle bugs or crashes. The offending code is typically very hard to track down since the problem is timing sensitive and the corruption can vary from run to run.
Helping Developers Write Better Code
In theory, it is very easy to avoid making UIKit calls from a background thread, it only takes discipline. But some developers are not even aware that they are making UIKit calls from a background thread because their code so far has not crashed (they have been mostly luck). Another problem is that software is continuously evolving, and it is possible for developers to accidentally use UIKit APIs from a background thread during a refactoring pass, or when new features are introduced by a team members that was not aware of the clean split.
With MonoTouch 5.4 we have introduced a feature that will help you track incorrect uses of UIKit from a background thread.
Starting with this release, debug builds of your application will throw a UIKitThreadAccessException exception if you try to invoke a UIKit method from a background thread.
This is what the exception will look like:
MonoTouch.UIKit.UIKitThreadAccessException: UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread. at MonoTouch.UIKit.UIApplication.EnsureUIThread at MonoTouch.UIKit.UIView.get_Subviews at Sample.AppDelegate.m__0
This is a fabulous tool. It founds bugs in my own code within a few seconds of me using my own software. Sometimes the bug is right there for you to see, but do not notice the mistake.
The Whitelist
Over time, Apple has made some of UIKit APIs thread safe. This means that there are certain APIs that can safely be used concurrently by both the main thread and background threads. Those are documented in MonoTouch's documentation
Our list is based on what Apple has publicly documented as thread safe in different forums. It is likely that more types and methods will become thread safe in the future, and when that happens, MonoTouch will will remove the particular check for debug builds for it.
Controlling The Thread Safety Check
By default MonoTouch is configured to perform the thread checks only on debug builds of your software. If you want to have these checks performed also during release builds, you can pass the --force-thread-check to the mtouch compiler.
You might want to disable this check for a couple of reasons. You might have a big infringing codebase that is mostly working for you now, and can not afford to go fix these bugs right away. Or you could get confirmation from Apple that it is safe to call an API from a background thread. With MonoTouch, we have opted to be conservative and go by what is documented, but it is very possible that there are some APIs that are thread safe and just have not been documented as such.
You can disable the feature for debug builds by passing the --disable-thread-check flag to the compiler, or you can do this at runtime by changing the value of UIApplication.CheckForIllegalCrossThreadCalls, like this:
// // Disable UIKit thread checks for a couple of methods // var previous = UIApplication.CheckForIllegalCrossThreadCalls; UIApplication.CheckForIllegalCrossThreadCall = false; // Perform some UIKit calls here foo.Bar = 1; // Restore UIApplication.CheckForIllegalCrossThreadCalls = previous;
Adding Your Own Checks
If are building a library that wants to enforce the same kind of checks, you should call the new UIApplication.EnsureUIThread from your code to perform these checks.