Dealing With GUI Threading Issues
Everybody at one point or another runs into a need to deal with threading. I would say the most common occasion is within event handlers. Often something on the application’s form will be updated to reflect information provided in the event handler. The problem with this is that the event handler will frequently be called by code that is running in its own thread.
Let’s say you are using one of the asynchronous methods of the WebClient class. If you try to display the content received in say, a textbox, the debugger will throw an invalid cross-threading exception. While you can technically ignore this as the error will only appear in the debugger, it is NOT recommended as it can lead to stability problems that can be difficult to track down.
Most guides to threading are fairly advanced and often include overly complicated examples. The code below is going to be short and to the point. So let’s get started.
In the example we will use the DownloadStringCompleted event of the WebClient (triggered by a call to WebClient.DownloadStringAsync).
C#
delegate void d_StringCompleted(object sender,
DownloadStringCompletedEventArgs e); void w_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { if (InvokeRequired) { d_StringCompleted callback = w_DownloadStringCompleted; BeginInvoke(callback, new object[] { sender, e }); } else { TextBox1.Text = e.Result; } }
VB.NET
Delegate Sub d_StringCompleted(ByVal sender As Object,
ByVal e As DownloadStringCompletedEventArgs) Private Sub w_DownloadStringCompleted(ByVal sender As Object,
ByVal e As DownloadStringCompletedEventArgs) If InvokeRequired Then Dim callback As d_StringCompleted = w_DownloadStringCompleted BeginInvoke(callback, New Object() {sender, e}) Else TextBox1.Text = e.Result End If End Sub
The first line defines our delegate. The idea is to declare the same parameters in the delegate as in the method that will be updating the control. In this case we are just calling the event handler again.
In the first line of the event, we check if InvokeRequired is True. If the code is executing on the same thread as the form, this will be false. Since our event is asynchronous, InvokeRequired will be true as it is called from another thread.
Next we must declare an instance of our delegate, and assign it to the method we’ll be calling. Since we’re calling the same method again, we assign it to w_DownloadStringCompleted.
We then call BeginInvoke. This is used to execute a function on the main (GUI) thread. The first argument is our delegate. The second argument is an Object array containing, in order, the arguments that the callback function expects. Here we simply enter the same variables that are passed to the event, sender and e.
Finally, the function is executed again on the main thread. This time, InvokeRequired is false, so we are safe to alter the contents of the TextBox.