UI Controls and Threading
Steve posted about a problem with updating a UI control from a non-UI thread. In his case, the exception that was produced wasn't clear as to what was causing the problem. On the mobile device the exception that was generated was:
NotSupportedException
An error message cannot be displayed because an optional resource assembly containing it cannot be found.
Nice, hey? Well, Threading 101 says that you should never update the UI from a non-UI thread - there is only one, and the control classes know whether they are executing on the UI thread or on another thread, through the InvokeRequired property, and, thankfully, you can execute code on the UI thread using the Invoke method of the control (or form).
Anyway, the pattern that event handlers should be using is this:
Public Sub OK_Click(sender as Object, e as EventArgs) Handles OK.Click
If Me.InvokeRequired Then
Me.Invoke(New EventHandler(AddressOf Me.OK_Click), New Object(){sender, e}))
Else
Me.TextBox1.Text = "Something"
End If
End Sub
The Smart Client Composite Application Block has an eventing mechanism that allows you to publish events from any object, and subscribe to them from any object, simply by using string constants for the event names in an attribute. Look at the EventBroker namespace for more details. The nice thing about subscribing to events is that you can specify that you want the event handler to be fired on the UI thread and the event broker does that for you, so you don't need to do the Me.InvokeRequired check.
Unfortunately, the standard Microsoft controls don't always follow this practice in their event handlers.
For example, recently I was working on a project that had a custom collection of business objects, implemented as a BindingList<of MyEntity>. My class MyEntity implemented the interface System.ComponentModel.INotifyPropertyChanged, and whenever I changed a property value, I raised the PropertyChanged event. The nice thing about inheriting from BindingList and using the PropertyChanged event is that the UI binding is already handled for you. I set the data source of a DataGrid control in my form to this inherited BindingList and the list nicely displayed in my grid.
However, when I had a background thread add something to my BindingList, it would cause a threading exception, because the DataGridView's handling of the ListChanged events from the BindingList didn't check for Me.InvokeRequired.
I found I had a few possible approaches to this problem:
- Inherit the DataGridView control and override the event handlers to "do it right". (nope, the handler methods aren't exposed)
- Get a System.Windows.Forms.WindowsFormsSynchronizationContext handle in my inherited BindingList and use its methods to add stuff to the list on the UI thread (ugly: I didn't want a reference to System.Windows.Forms in the business assembly I had that list in, and this just moves the Me.Invoke stuff into the middle tier. But I did implement this mechanism for a time then decided to remove it.)
- Implement the BindingList in the UI layer and have the business layer raise events about new or changed entities - all the list management happens in the UI by responding (on the UI thread) to the events from the business layer (this is the one I chose in this case, because maintaining a list of entities wasn't needed in the business layer).
Throughout this process, I keep thinking that I wish that I could just mark an event handler as needing to execute on the UI thread and let the .NET Framework do this for me. An attribute like <UIThreadExecute> for an event handler could "just do it" for me. So the above code example would end up looking like this instead:
<UIThreadExecute()> _
Public Sub OK_Click(sender as Object, e as EventArgs) Handles OK.Click
Me.TextBox1.Text = "Something"
End Sub
Wouldn't that be simple?