Synchronizing Data for Multithreading

Microsoft Silverlight will reach end of support after October 2021. Learn more.

When multiple threads can make calls to the properties and methods of a single object, it is critical that those calls be synchronized. Otherwise, one thread might interrupt what another thread is doing, and the object could be left in an invalid state. See the discussion of race conditions in Best Practices for Managed Threading.

A class whose members are protected from such interruptions in all cases is called thread-safe. In general, the classes in the .NET Framework class libraries are not thread-safe, because making a class thread-safe usually reduces performance and may also increase the possibility of deadlocks. Instead, the .NET Framework provides techniques that enable you to synchronize access to data only when you need to. The only classes that are thread-safe are the ones that must be thread-safe, like the Thread class.

Synchronization mechanisms in Silverlight can be divided into two broad categories, implicit and explicit. Implicit synchronization, as exemplified by the BackgroundWorker and Dispatcher classes, is simpler. Explicit synchronization, as exemplified by the Monitor and WaitHandle classes, provides a richer set of features. For more information about explicit synchronization, see Synchronization Primitives.

Implicit Synchronization

Implicit synchronization occurs when events raised by the BackgroundWorker class or cross-thread calls made by using the Dispatcher.BeginInvoke method are marshaled to the main application thread (the UI thread) of your Silverlight-based application. The events and cross-thread calls are marshaled sequentially. If one occurs while another is already executing or while the UI thread is executing a user-initiated event, the second blocks until the first is finished.

This synchronization model is very similar to a single-threaded, event-driven application model. The entire state of the user interface is protected from concurrent access because all calls from the outside occur sequentially. Event-driven programs are written under the assumptions that any event might occur at any time, and that at the end of any event the application is in a consistent state.

This is a fairly coarse-grained synchronization scheme, but it is effective for many simple synchronization scenarios. For example, a background task that is being executed by a BackgroundWorker object communicates its completion by raising an event on the UI thread. In the handler for this event, you might enable buttons or other UI elements that were disabled while the background task was executing. Figuring out what user actions need to be disabled while the background task executes is a fairly straightforward job.

Treating Cross-Thread Dispatcher Calls Like Events

Similarly, when you get the Dispatcher object for a UI element and use its BeginInvoke method to execute a helper method that accesses one or more UI elements, the call to the helper method is automatically synchronized when it is marshaled to the UI thread. Effectively, the helper method becomes another event.

Treating the helper method as an event greatly simplifies the job of keeping your application data in a consistent state. For example, before you start a thread that will communicate its results by using the Dispatcher.BeginInvoke method, you can disable any UI elements whose user-triggered actions might conflict with the task that is performed by the thread.

Most of the examples for the classes in the System.Threading namespace use the Dispatcher object to communicate with the UI thread.