Code Focused

Threading and the UI

Four ways to synchronize threads with your app's user interface.

When Visual Basic moved to the .NET platform it gained support for free threading, allowing you to easily spawn new threads in your application, control synchronization and-most importantly-be able to debug multi-threaded applications. But two old adages apply here: "There's no such thing as a free lunch" and "With great power comes great responsibility." As long as UI's remain limited to the one thread, there remains the need to synchronize thread notifications back to the UI thread.

For those familiar with VB6, you probably remember the Coffee sample application that shipped with VB6 demonstrating how to use an active thead for background threading. Compared to .NET it was a lot of rigmarole. You had to call an ActiveX .EXE, and in that spawn a timer or something similar to then start your second thread. And you had to use multiple instances of the VB6 IDE to get any semblance of debugging to work. These limitations, which are manifestations of the VB6 apartment-threading model, made creating threads painful. But at the same time, they allowed VB6 to hide the marshalling that was going on: In your main application the event notifications were all on the UI thread.

Fast forward to Visual Basic.NET, where you can easily create threads: The VB.NET compiler and runtime don't force you to work on a single thread. The power and freedom to control threads puts the onus back on you to ensure that notifications are marshaled to the UI thread when necessary.

You need to be aware of the different ways you can achieve synchronization with the UI and apply the right technique on an as-needed basis. There are a number of techniques you can use, but I'll show you four that cover the vast majority of cases you'll come across.

The first and perhaps simplest technique is to use the Control.Invoke method to marshal the call over to the UI thread. You first check to see if InvokeRequired is true, and if it is you call Control.Invoke. You can pass to Control.Invoke a delegate, which points to the same method you're currently in, but when it gets called via Invoke it will be on the UI thread.

Private Sub SomethingChanged( _
                   ByVal sender As Object, _
                   ByVal ev As EventArgs)

   If Me.InvokeRequired Then
       Me.Invoke( _
              New EventHandler(Of EventArgs) _
                  (AddressOf SomethingChanged), _ 
              sender, ev)
       Return
  End If
  MsgBox("this is on the UI thread")
End Sub

If the SomethingChanged method is called on the UI thread, the InvokeRequired property of the Form returns False and the code jumps straight to the message box call. If, however, the method is called from another thread, then InvokeRequired would be True and the Form's Invoke method is used to call the SomethingChanged method again, this time on the UI thread. You can apply this simple pattern in any event handler or method you expect might be called from a non-UI thread.

A second approach you can employ is to use the BackgroundWorker component. The BackgroundWorker component first shipped with .NET 2.0 and is in the Windows.Forms namespace. It wraps the threading by exposing events DoWork, ProgressChanged and RunWorkerCompleted. The DoWork event is raised on a new thread allocated from the thread pool, whereas the ProgressChanged and RunWorkerCompleted events are raised on the context of the initialization of the component, which when used with a control or form will be the UI thread.

You launch the BackgroundWorker process by calling its RunWorkerAsync method:

    BackgroundWorker1.RunWorkerAsync

In the DoWork event handler, you write the code you want to run on the background thread. In that code you can opt to raise progress changed events by calling ReportProgress. You can also opt to handle any cancel request and terminate the thread early:

Private Sub BackgroundWorker1_DoWork( _
                ByVal sender As Object,  _
                ByVal e As DoWorkEventArgs) _
                Handles BackgroundWorker1.DoWork

   'do the async work here
   For i As Int32 = 1 To 100
      If BackgroundWorker1.CancellationPending Then
         e.Cancel = True
         e.Result = Nothing
         Return
      End If
      BackgroundWorker1.ReportProgress(i)
   Next
   e.Result = "anything you want to pass to" + _ 
				   "the completed event"
End Sub

You then handle the ProgressChanged and RunWorkerCompleted events, updating the UI as required.

These two techniques, Control.Invoke and use of the BackgroundWorker, are suitable for cases where you're imperatively modifying the UI from an event or notification that occurs on another thread. If the BackgroundWorker pattern suits your application model-when, for example, you have control over the thread start-it makes your code a lot clearer and easier to maintain compared to making calls to Control.Invoke. Save calls to Control.Invoke for the cases where you don't have control over the thread start.

Control.Invoke and BackgroundWorker only come into play when you're directly intercepting the events or notifications. You marshal that notification to the UI thread and then, as needed, alter the control's properties. That's imperative style coding. In the case where you're data binding and the properties of your data change on another thread, you don't have the methods in your form's code to intercept. For data binding, you need to take a slightly different approach.

In .NET 2.0 Microsoft added the BindingSource component to serve as a brokerage between your data and the form's data bindings. When you drag and drop data from the data window onto a form, a BindingSource is added to the form's components. The BindingSource is the perfect place to intercept change notifications from your data and ensure that notification is bubbled up onto the UI thread. The BindingSource provides notifications to the form's data bindings via the IBindingList interface. IBindingList only has one event notification: ListChanged. Hence the perfect place to intercept the thread is in the BindingSource's OnListChanged method.

You can do this by deriving from BindingSource and overriding the base OnListChanged method:

Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms

<DesignerCategory("")> _
Public Class BindingSourcePlus
   Inherits BindingSource
   
   Protected Overrides Sub OnListChanged( _
                 ByVal e As ListChangedEventArgs)
      'Your code to synchronize the threads goes here
   End Sub

End Class

There are a few options available to you to synchronize the threads. One approach you can use is to examine the BindingSource's CurrencyManager property, then from the CurrencyManager get the Bindings property, then enumerate the bindings and get the Control property of each and see if InvokeRequired is True. If so, you use the control's invoke method:

Private Function GetInvokeRequiredControl() _
		    As Control
   If Me.CurrencyManager IsNot Nothing AndAlso _
      Me.CurrencyManager.Bindings IsNot Nothing Then
      For Each bind As Binding In _
      	      Me.CurrencyManager.Bindings
            Dim cntl As Control = bind.Control
            If cntl IsNot Nothing AndAlso _
                 cntl.InvokeRequired Then
               Return cntl
            End If
      Next
   End If
   Return Nothing
End Function

Protected Overrides Sub OnListChanged( _
              ByVal e As ListChangedEventArgs)
   Dim cntl As Control = GetInvokeRequiredControl()
   If cntl IsNot Nothing Then
      cntl.Invoke(New Action(Of ListChangedEventArgs) _
      ( AddressOf OnListChangedInvoke), e)
      Else
         OnListChangedInvoke(e)
   End If
End Sub

Private Sub OnListChangedInvoke( _
          ByVal e As ListChangedEventArgs)
   MyBase.OnListChanged(e)
End Sub

Although this code works, it's far from optimal. The problem is it looks at each binding and its control property. In the cases where you aren't using threading, the code would have to cycle through all the bindings. You could optimize it by making assumptions such as: If the first control doesn't require invoke, none of the others will. You can't cache whether the control requires invoke or not, because the notification could come on a different thread and the controls might be added or removed. A trap for the unwary is that the OnListChanged method is raised when the binding source is first instantiated, before any of the bindings have been added.

In the custom binding source component that I use these days, I don't iterate through the bindings at all. Rather, I make the assumption that binding source is instantiated on the UI thread. Based on that assumption, I store a SynchronizationContext in the constructor, and later I use it to invoke the ListChanged notification.

The SynchronizationContext class was added to .NET Framework in version 2.0 to simplify asynchronous operations. You use the SynchronizationContext's Post method to invoke notification asynchronously. That is to say, if the notification is on a different thread from the UI's SynchronizationContext, the Post method will invoke that notification asynchronously back to the UI thread.

Using the SynchronizationContext, the custom binding source class can be written thus:

Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms

<DesignerCategory("")> _
Public Class BindingSourcePlus
   Inherits BindingSource

   Private _context As SynchronizationContext
   Private _postback As New SendOrPostCallback( _
                  AddressOf Me.OnListChangedPost)

   Public Sub New(ByVal container As IContainer)
      MyBase.New(container)
      _context = AsyncOperationManager _
				 .SynchronizationContext
   End Sub

   Protected Overrides Sub OnListChanged( _
            ByVal e As ListChangedEventArgs)
      If _context Is Nothing Then
         MyBase.OnListChanged(e)
      Else
         _context.Post(_postback, e)
      End If
   End Sub

   Private Sub OnListChangedPost(ByVal e As Object)
      MyBase.OnListChanged( _
                TryCast(e, ListChangedEventArgs))
   End Sub

End Class

Once you've added the BindingSourcePlus component to your project, you need to modify any designer-generated binding sources to be of your custom binding source type. To do this you can click on the "Show All Files" button in Solution Explorer, expand the forms in the tree view, open their .designer.vb file and manually make the changes in there.

Alternatively, you can use Quick Replace to replace all instances of "System.Windows.Forms.BindingSource" with "BindingSourcePlus." Make sure your search options include searching in hidden files. You should have two replacements per binding source.

Another technique to use instead of a custom binding source is to make your data raise change notifications on the SynchronizationContext. This approach takes the onus off anyone developing UIs for your data; however, it does require adding code to your data class.

In particular, this method requires that you declare the PropertyChanged event using custom events. This requirement means you have to put the synchronization code in the base class that implements INotifyPropertyChanged.

The PropertyChanged event is at the heart of the data binding change notification. Typically a simple implementation pattern checks to see if the underlying value of a property is changed, and if so it raises a PropertyChanged event:

Public Class TestDataObject
   Implements INotifyPropertyChanged

   Private _Name As String

   Public Property Name() As String
      Get
         Return _Name
      End Get
      Set(ByVal value As String)
         If _Name <> value Then
            _Name = value
            OnPropertyChanged("Name")
         End If
      End Set
   End Property


   Protected Sub OnPropertyChanged( _
                        ByVal propertyName As String)
      RaiseEvent PropertyChanged(Me,  _
       New PropertyChangedEventArgs(propertyName))
   End Sub


   Public Event PropertyChanged As  _
            PropertyChangedEventHandler Implements _
            INotifyPropertyChanged.PropertyChanged

End Class

To make the PropertyChanged event fire on the SynchronizationContext of the listening code, you will need to modify the PropertyChanged event declaration to a custom event handler. To do this, simply put the keyword Custom before the Event keyword and then hit enter at the end of the line. The IDE will add the custom AddHandler, RemoveHandler and RaiseEvent stubs for you:

Public Custom Event PropertyChanged As _
               PropertyChangedEventHandler Implements _
               INotifyPropertyChanged.PropertyChanged
   AddHandler(ByVal value As _
                         PropertyChangedEventHandler)
    End AddHandler

    RemoveHandler(ByVal value As _
                          PropertyChangedEventHandler)

      End RemoveHandler

      RaiseEvent(ByVal sender As Object,  _
               ByVal e As PropertyChangedEventArgs)

      End RaiseEvent
   End Event

In the AddHandler block you need to store the handler being passed in, and at that point in time also store the SynchronizationContext to be used later when raising the event. As each handler may have a different context, you'll need to have a storage class for each handler and its context:

Public Class PropertyChangedEventContext
   Private _handler As PropertyChangedEventHandler
   Private _context As SynchronizationContext

   Public Sub New(ByVal handler As _
              PropertyChangedEventHandler)
      _handler = handler
      _context = SynchronizationContext.Current
   End Sub

   Public ReadOnly Property Handler() As _
                    PropertyChangedEventHandler
      Get
         Return _handler
      End Get
   End Property

   Public Sub Raise(ByVal sender As Object,  _
                ByVal e As PropertyChangedEventArgs)
      Static propertyChangedPost As SendOrPostCallback
      If _context Is Nothing Then
         _handler.Invoke(sender, e)
      Else
         If propertyChangedPost Is Nothing Then
           propertyChangedPost = New _
SendOrPostCallback( AddressOf OnPropertyChangedPost)
          End If
            _context.Post(propertyChangedPost, _
                            New Args(sender, e))
      End If
   End Sub

   Private Sub OnPropertyChangedPost( _
             ByVal state As Object)
      Dim args = CType(state, Args)
      _handler.Invoke(args.sender, args.ev)
   End Sub

   Private Class Args
      Public sender As Object
      Public ev As PropertyChangedEventArgs
      Public Sub New(ByVal senderObject As Object, _
                ByVal e As PropertyChangedEventArgs)
         Me.sender = senderObject
         Me.ev = e
      End Sub
   End Class
End Class

The PropertyChangedEventContext class exposes a Raise method that does all the checking to see if the SynchronizationContext is nothing or not, and invokes the delegate accordingly. After you add the PropertyChangedEventContext class to your project, go back to your custom PropertyChanged event code and modify as follows:

   Private _propertyChangedHandlers As _
            New List(Of PropertyChangedEventContext)

   Public Custom Event PropertyChanged As _
          PropertyChangedEventHandler Implements _
              INotifyPropertyChanged.PropertyChanged
      AddHandler(ByVal value As _
                    PropertyChangedEventHandler)
         _propertyChangedHandlers.Add(New _
                     PropertyChangedEventContext(value))
      End AddHandler

      RemoveHandler(ByVal value As _
               PropertyChangedEventHandler)
         Dim i as Int32
         For i = _propertyChangedHandlers.Count - 1 _
			To 0 _ Step -1
              If _propertyChangedHandlers(i).Handler _
                             Is value Then
               _propertyChangedHandlers.RemoveAt(i)
               Return 'remove only the last one
            End If
         Next
      End RemoveHandler

      RaiseEvent(ByVal sender As Object,  _
               ByVal e As PropertyChangedEventArgs)
         For Each item In _propertyChangedHandlers
            item.Raise(sender, e)
         Next
      End RaiseEvent
   End Event

Your class will now raise its PropertyChanged events on the same context from which the event listeners were added. For a Windows.Forms application this will typically be the UI thread, as the binding is all done in the form's designer-generated code.

Although this last approach seems to be a bit of code, once you have added the PropertyChangedEventContext to your project, you only need write the custom event code. To make your life even easier, you can use a snippet for the custom event code: I've included one such snippet with the sample code that accompanies this article, which you can find online at VisualStudioMagazine.com.

In this article you've learned about four different ways to achieve threading synchronization with your application's UI: use Control.Invoke or the BackgroundWorker when coding imperatively; use a custom binding source when you're data binding and you can't modify the actual data classes; or modify your data classes to raise the PropertyChanged event using a SynchronizationContext.

In the future some of this maybe further simplified. Concurrent Basic, which is an experimental version of VB designed with threading at its heart, has a nice declarative way to mark a method as being invoked on the UI thread. You simply add a <UI()> attribute to the method declaration and the compiler will add the necessary code to marshal calls back to the UI thread when calling that method.

For more on some of the exciting new frontiers of VB with concurrency, check out the Concurrent basic research site at http://tinyurl.com/lkuuzm.

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube