Many of us may be familiar with the restriction against modifying a Control from any thread other than the one in which it was created, if we do so we get a CrossThreadOpeartionException. Instead, an event generated on another thread must be marshaled to the Control’s thread using its (or the Form that it belongs to) Invoke or BeginInvoke methods. These two methods belong to the ISynchronizeInvoke interface, which the Control class implements. Their purpose is to take a delegate and invoke it on the same thread that the ISynchronizeInvoke object is running. Have a look at this tip by Rabinarayan Biswal In the above tip, the method DisplayErrorOnUI() first checks its InvokeRequired boolean property, which also belongs to the ISynchronizeInvoke interface, to see if it needs to marshal the event to its thread. If the property is true, it calls the BeginInvoke method, passing it a delegate to the method for handling the event and its arguments. If the property is false, it executes its logic for responding to the event. In other words, the InvokeRequired property will be true if it is checked on a thread other than the one in which the Form belongs; otherwise, it will be false. But, wouldn’t it be nice if Forms didn’t have to check to see if an event was raised on another thread? Wouldn’t it be nice if a Form could respond to an event without having to check its InvokeRequired property? This is the place where SynchronizationContext class comes to our rescue. The SynchronizationContext class gives us a way through which we can pass delegates to be invoked on the same thread that the SynchronizationContext represents. It is like the ISynchronizeInvoke interface in that respect. Corresponding to the ISynchronizeInvoke’s Invoke and BeginInvoke methods, the SynchronizationContext class has Send and Post methods. Like the Invoke method, the SynchronizationContext’s Send method is for invoking delegates synchronously, and like the BeginInvoke method, the Post method is for invoking delegates asynchronously. |
Both the Send and Post methods take a SendOrPostCallback delegate representing the method to be invoked. In addition, they also take an object representing state information to pass to the delegate when it is invoked. This is how it can be done : |
namespace SynchronizationContextDemo { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnStart_Click(object sender, EventArgs e) { new Thread(new ThreadStart(new AnotherClass(this).DoWork)).Start(); } public void AnotherDoWork(string text) { lblMsg.Text = text; } } class AnotherClass { private Form1 formObject; private SynchronizationContext context; public AnotherClass(Form1 form) { formObject = form; context = SynchronizationContext.Current; if (context == null) { context = new SynchronizationContext(); } } public void DoWork() { context.Post(new SendOrPostCallback(delegate { formObject.AnotherDoWork("Hello World !"); }), null); } } }
Here, when a Form is created, a SynchronizationContext object representing the Form’s thread is set. Other objects can retrieve the Form’s SynchronizationContext object through the Current property and use it later for marshaling delegate invocations to the Form’s thread. In this way, Forms no longer have to check to see if an event they are responding to originated on another thread. The marshaling has already been taken care of elsewhere. In essence, the responsibility for marshaling events has been moved from the receiver of an event to the sender.