Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF
Article

Responsive UIs for WPF Applications Using Asynchronous Processing

Rate me:
Please Sign up or sign in to vote.
4.64/5 (21 votes)
5 Oct 2008CPOL16 min read 117K   4K   66   10
Demonstrates how to keep WPF UIs responsive while running long processes, through asynchronous programming.

Introduction

Goal: Demonstrate how to keep WPF UIs responsive while running long processes asynchronously.

Method(s): Through a sample application, I will first demonstrate what a non-responsive UI is and how you get one. Next, I will demonstrate how to make the UI responsive through asynchronous code. Finally, I will demonstrate how to make the UI responsive through a much simpler, event-based, asynchronous approach. I will also show how to keep the user informed while the processing takes place, with updates to TextBlocks and a ProgressBar.

What is a non-responsive UI? Surely, we’ve all witnessed a Windows Form or WPF application that “locks up” from time to time. Have you ever thought of why this happens? In a nutshell, it’s typically because the application is running on a single thread. Whether it’s updating the UI or running some long process on the back end, such as a call to the database, everything must get into a single file line and wait for the CPU to execute the command. So, when we are making that call to the database that takes a couple seconds to run, the UI is left standing in line waiting, unable to update itself, and thus “locking up”.

How can this unresponsive UI problem be resolved? Whether it’s a Windows Form or WPF application, the UI updates on the main or primary thread. In order to keep this thread free so the UI can remain responsive, we need to create a new thread to run any large tasks on the back-end. The classes used to accomplish this have evolved over the different releases of the .NET Framework, becoming easier, and richer in capabilities. This, however, can cause some confusion. If you do a simple Google search on C# or VB and asynchronous, or something similar, you are sure to get results showing many different ways of accomplishing asynchronous processing. The answer to the question, “which one do I use?” of course depends on what you’re doing and what your goals are. Yes, I hate that answer also.

Since I cannot possibly cover every asynchronous scenario, what I would like to focus on in this article is what I have found myself needing asynchronous processing for majority of the time. That would be keeping the UI of a WPF application responsive while running a query on the database. Please note that with some minor modifications, the code in this article and in the downloadable source code can be run for a Windows Form application also. In addition, this article shows how to solve a specific problem with asynchronous programming, by no means though is this the only problem asynchronous programming is used for.

To help demonstrate synchronous, asynchronous, and event-driven asynchronous processing, I will work through an application that transgresses through several demos:

  • Synchronous demo (what not to do): Handle all processing on a single thread and lock up the UI.
  • Asynchronous demo: Add a secondary thread to free up the UI. I will also add some responsive text to the UI as a visual indicator to let the user know where things are at.
  • Asynchronous Event-Based Model demo: With this, I will also add a progress bar and some responsive text.

The code in this article will be written in VB; however, full source code download will be available in both C# and VB versions.

What Not To Do

lockedUpUI.jpg

As I mentioned previously, what you do not want to do is run all your processing both back-end and UI on a single thread. This will almost always lead to a UI that locks up. You can download the demo application in both C# and VB versions. Run the application, and click the Start button under Synchronous Demo. As soon as you click the button, try to drag the window around your screen. You can’t. If you try it several times, the window may even turn black, and you will get a “(Not Responding)” warning in the title bar. However, after several seconds, the window will unlock, the UI will update, and you can once again drag it around your screen freely.

Let’s look at this code to see what’s going on. If you look at the code for this demo, you will see the following:

First, we have a delegate which is sort of like a function pointer, but with more functionality and providing type safety.

VB
Delegate Function SomeLongRunningMethodHandler(ByVal rowsToIterate As Integer) As String

We could easily not use the delegate in this sample, and simply call the long running method straight from the method handler. In fact, if I didn't already know I was going to change this call to run asynchronously, I wouldn't use a delegate. However, by using the delegate, I can demonstrate how easy it is to go from a synchronous call to an asynchronous call. In other words, let’s say you have a method that you may want to run asynchronously but you aren’t sure. By using a delegate, you can make the call synchronously now, and later switch to an asynchronous call with little effort.

I’m not going to go into too much detail on delegates, but the key to remember is that the signature of the delegate must exactly match the signature of the function (or Sub in VB) it will later reference. In this VB example, the delegate signature is for a Function that takes an Integer as a parameter and returns a String.

Next, we have the method handler for the Click event of the button. After resetting the TextBlock to an empty string, the delegate is declared. Then, the delegate is instantiated (yes, a class is created when you create a delegate). In this case, a pointer to the function to be called by the delegate is passed as a parameter to the constructor. What we now have is an instance of our delegate (synchronousFunctionHandler) that points to the function SomeLongRunningSynchronousMethod. If you move down one more line, you can see how this method is called synchronously by the delegate. The delegate instance we have is actually an instance of a class with several methods. One of those methods is called Invoke. This is how we synchronously call the method attached to the delegate. You may have also noticed the methods BeginInvoke and EndInvoke, if you used Intellisense.

Remember when I said that by using delegates we can easily move from synchronous to asynchronous? You now have a clue as to how, and we will get into the details of that soon.

Going back to our asynchronous example, you can see the Invoke method is called on the delegate instance. It is passed an integer as a parameter, and returns a string. That string is then assigned to a TextBlock to let the user know the operation is complete.

VB
Private Sub SynchronousStart_Click(ByVal sender As System.Object, _
    ByVal e As System.Windows.RoutedEventArgs) _
    Handles synchronousStart.Click

    Me.synchronousCount.Text = "" 

    Dim synchronousFunctionHandler As SomeLongRunningMethodHandler 

    synchronousFunctionHandler = _
        New SomeLongRunningMethodHandler(AddressOf _
        Me.SomeLongRunningSynchronousMethod)

    Dim returnValue As String = _
        synchronousFunctionHandler.Invoke(1000000000) 

    Me.synchronousCount.Text = _
        "Processing completed."&

    returnValue & " rows processed."

End Sub

This is the function that the delegate calls. As mentioned earlier, it could have also been called directly without the use of a delegate. It simply takes an integer, and iterates that many times, returning the count as a string when completed. This method is used to mimic any long running process you may have.

VB
Private Function SomeLongRunningSynchronousMethod _
        ByVal rowsToIterate As Integer) As String

     Dim cnt As Double = 0

     For i As Long = 0 To rowsToIterate
          cnt = cnt + 1
     Next

     Return cnt.ToString()

End Function

The bad news is that implementing this demo asynchronously causes an unresponsive UI. The good news is that by using a delegate, we have set ourselves up to easily move to an asynchronous approach and a responsive UI.

A More Responsive Approach

Now, run the downloaded demo again, but this time, click the second Run button (Synchronous Demo). Then, try to drag the window around your screen. Notice anything different? You can now click the button which calls the long running method and drag the window around at the same time, without anything locking up. This is possible because the long running method is run on a secondary thread, freeing up the primary thread to handle all the UI requests.

asynchronousDemo.jpg

This demo uses the same SomeLongRunningSynchronousMethod as the previous example. It will also begin by declaring and then instantiating a delegate that will eventually reference the long running method. In addition, you will see a second delegate created with the name UpdateUIHandler, which we will discuss later. Here are the delegates and the event handler for the button click of the second demo:

VB
Delegate Function AsyncMethodHandler _
     ByVal rowsToIterate As Integer) As String

Delegate Sub UpdateUIHandler _
     ByVal rowsupdated As String)

Private Sub AsynchronousStart_Click( _
     ByVal sender As System.Object, _
     ByVal e As System.Windows.RoutedEventArgs) 

     Me.asynchronousCount.Text = ""
     Me.visualIndicator.Text = "Processing, Please Wait...."
     Me.visualIndicator.Visibility = Windows.Visibility.Visible
     Dim caller As AsyncMethodHandler 

     caller = New AsyncMethodHandler _
     (AsyncMethodHandlerAddressOf Me.SomeLongRunningSynchronousMethod)

     caller.BeginInvoke(1000000000, AddressOf CallbackMethod, Nothing) 

End Sub

Notice that the event method starts out similar to the previous example. We setup some UI controls, then we declare and instantiate the first delegate. After that, things get a little different. Notice the call from the delegate instance “caller” to BeginInvoke. BeginInvoke is an asynchronous call, and replaces the call to Invoke seen in the previous example. When calling Invoke, we passed the parameter that both the delegate and the delegate method had in their signature. We do the same with BeginInvoke; however, there are two additional parameters passed which are not seen in the delegate or the delegate method signature. The two additional parameters are DelegateCallback of type AsyncCallback and DelegateAsyncState of type Object. Again, you do not add these two additional parameters to your delegate declaration or the method the delegate instance points to; however, you must address them both in the BeginInvoke call.

Essentially, there are multiple ways to handle asynchronous execution using BeginInvoke. The values passed for these parameters depend on which technique is used. Some of these techniques include:

  • Call BeginInvoke, do some processing, call EndInvoke.
  • Using a WaitHandle of type IAsyncResult returned by BeginInvoke.
  • Polling using the IsCompleted property of the IAsyncResult returned by BeginInvoke.
  • Executing a callback method when the asynchronous call completes.

We will use the last technique, executing a callback method when the asynchronous call completes. We can use this method because the primary thread which initiates the asynchronous call does not need to process the results of that call. Essentially, what this enables us to do is call BeginInvoke to fire off the long running method on a new thread. BeginInvoke returns immediately to the caller, the primary thread in our case, so UI processing can continue without locking up. Once the long running method has completed, the callback method will be called and passed the results of the long running method as a type IAsyncResult. We could end everything here; however, in our demo, we want to take the results passed into the callback method and update the UI with them.

You can see that our call to BeginInvoke passes an integer, which is required by the delegate and the delegate method as the first parameter. The second parameter is a pointer to the callback method. The final value passed is “Nothing”, because we do not need to use the DelegateAsyncState in our approach. Also, notice that we are setting the Text and Visibility property of the visualIndicator TextBlock here. We can access this control because this method is called on the primary thread, which is also where these controls were created.

VB
Protected Sub CallbackMethod(ByVal ar As IAsyncResult)

     Try

          Dim result As AsyncResult = CType(ar, AsyncResult)
          Dim caller As AsyncMethodHandler = CType(result.AsyncDelegate, _
               AsyncMethodHandler)

          Dim returnValue As String = caller.EndInvoke(ar)  

          UpdateUI(returnValue) 

     Catch ex As Exception
          Dim exMessage As String
          exMessage = "Error: " & ex.Message
          UpdateUI(exMessage)
     End Try 

End Sub

In the callback method, the first thing we need to do is get a reference to the calling delegate (the one that called BeginInvoke), so that we can call EndInoke on it and get the results of the long running method. EndInvoke will always block further processing until BeginInvoke completes. However, we don’t need to worry about that because we are in the callback method which only fires when BeginInvoke has already completed.

Once EndInvoke is called, we have the result of the long running method. It would be nice if we could then update the UI with this result; however, we cannot. Why? The callback method is still running on the secondary thread. Since the UI objects were created on the primary thread, they cannot be accessed on any thread other than the one which created them. Don’t worry though; we have a plan which will allow us to still accomplish updating the UI with data from the asynchronous call.

After EndInvoke is called, the Sub UpdateUI is called and is passed the return value from EndInvoke. Also notice that this method is wrapped in a Try-Catch block. It is considered a good coding standard to always call EndInvoke and to wrap that call in a Try-Catch if you wish to handle the exception. This is the only positive way to know that the asynchronous call made by BeginInvoke completed without any exceptions.

VB
Sub UpdateUI(ByVal rowsUpdated As String)

     Dim uiHandler As New UpdateUIHandler(AddressOf UpdateUIIndicators)
     Dim results As String = rowsUpdated

     Me.Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Normal, _
          uiHandler, results)

End Sub


Sub UpdateUIIndicators(ByVal rowsupdated As String)

     Me.visualIndicator.Text = "Processing Completed."

     Me.asynchronousCount.Text = rowsupdated & " rows processed."

End Sub

Next, we can see the UpdateUI method. It takes as a parameter the return value from EndInvoke in the callback method. The first thing it does is to declare and instantiate a delegate. This delegate is a Sub, and takes a single parameter of type String. Of course, this means that the function pointer it takes in its constructor must also point to a Sub with the exact same signature. For our demo, that would be the UpdateUIIndicators Sub. After setting up the delegate, we place the UpdateUI parameter into a string. This will eventually be passed into BeginInvoke.

Next, you will see the call to Invoke. We could have also used a call to BeginInvoke here, but since this method is only updating two UI properties, it should run quickly and with out the need for further asynchronous processing. Notice that the call to Invoke is run off Me.Dispatcher. The dispatcher in WPF is the thread manager for your application. In order for the background thread called by Invoke to update the UI controls on the primary thread, the background thread must delegate the work to the dispatcher which is associated to the UI thread. This can be done by calling the asynchronous method BeginInvoke, or the synchronous method Invoke as we have done off the dispatcher. Finally, the Sub UpdateUIIndicators takes the results passed into it and updates a TextBlock on the UI. It also changes the text on another TextBlock to indicate that processing has completed.

We have now successfully written a responsive multi-threaded WPF application. We have done it using delegates, BeginInvoke, EndInvoke, callback methods, and the WPF Dispatcher. Not a ton of work, but more than a little. However, this traditional approach to multithreading can now be accomplished using a much simpler WPF asynchronous approach.

Asynchronous Event-Based Model

asynchronousEventBasedDemo.jpg

There are many approaches to writing asynchronous code. We have already looked at one such approach, which is very flexible should you need it. However, as of .NET 2.0, there is what I would consider a much simpler approach, and safer. The System.ComponentModel.BackgroundWorker (BackgroundWorker) provides us with a nearly fail-safe way of creating asynchronous code. Of course, the abstraction which provides this simplicity and safety usually comes at a cost, which is flexibility. However, for the task of keeping a UI responsive while a long process runs on the back-end, it is perfect. In addition, it provides events to handle messaging for both the tracking process and the cancellation with the same level of simplicity.

Consider the following method which we have decided to spin off on a separate thread so that the UI can remain responsive.

VB
Private Function SomeLongRunningMethodWPF() As String

     Dim iteration As Integer = CInt(100000000 / 100)
     Dim cnt As Double = 0

     For i As Long = 0 To 100000000

          cnt = cnt + 1

          If (i Mod iteration = 0) And (backgroundWorker IsNot Nothing) _
               AndAlso backgroundWorker.WorkerReportsProgress Then

               backgroundWorker.ReportProgress(i \ iteration)

          End If

     Next

     Return cnt.ToString()

End Function

Notice, there is also some code to keep track of the progress. We will address this as we get to it; for now, just keep in mind we are reporting progress to the backgroundWorker.ReportProgress method.

Using the BackgroundWorker and the event driven model, the first thing we need to do is create an instance of the BackgroundWorker. There are two ways to accomplish this task:

  • Create the BackgroundWorker instance declaratively in your code.
  • Create the BackgroundWorker in your XAML markup as a resource. Using this method allows you to wire up your event method handles using attributes.

I will quickly demonstrate the latter method, but for the remainder of the demo, we will use the declarative approach.

First, you must reference the namespace for System.ComponentModel.

XML
<Window x:Class="AsynchronousDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cm="clr-namespace:System.ComponentModel;assembly=System"
    Title="Asynchronous Demo" Height="400" Width="450">

Then, you can create an instance of the BackgroundWorker. Since there is no UI element, you can drop this XAML anywhere on the page.

XML
<Window.Resources>
     <cm:BackgroundWorker x:Key="backgroundWorker" _
     WorkerReportsProgress="True" _
     WorkerSupportsCancellation="False" />
</Window.Resources>

Declaratively, we could accomplish the same thing:

VB
Private WithEvents backgroundWorker As New BackgroundWorker()

Next, we need something to call the long running process to kick things off. In our demo, we will trigger things with the Click event of the button. Here’s the method handler that gets called and starts things off:

VB
Private Sub WPFAsynchronousStart_Click(ByVal sender As System.Object, _
     ByVal e As System.Windows.RoutedEventArgs)

     Me.wpfCount.Text = ""
     Me.wpfAsynchronousStart.IsEnabled = False

     backgroundWorker.RunWorkerAsync()

     wpfProgressBarAndText.Visibility = Windows.Visibility.Visible

End Sub

Let’s go through what’s happening in the button click event. First, we clear out any text that’s in our TextBlock used for displaying messages on the UI, and set the IsEnabled state of the two buttons. Next, we call RunWorkerAsync, which fires off a new thread and begins our asynchronous process. The event that is called by RunWorkerAsync is DoWork. DoWork, which is running on a new thread, provides us a place to call our long running method. RunWorkerAsync also has a second overload, which takes an Object. This object can be passed to the DoWork method, and used in further processing. Note that we do not need any delegates here, and we do not need to create any new threads ourselves.

When the button is clicked, we are also capturing that event in a Storyboard located in the XAML. This Storyboard triggers the animation directed at a ProgressBar, which runs until the asynchronous process has completed.

XML
<StackPanel.Triggers>
     <EventTrigger RoutedEvent="Button.Click" 
          SourceName="wpfAsynchronousStart">
               <BeginStoryboard Name="myBeginStoryboard">
                    <Storyboard Name="myStoryboard" 
                         TargetName="wpfProgressBar" 
                         TargetProperty="Value">
                              <DoubleAnimation 
                                   From="0" 
                                   To="100"
                                   Duration="0:0:2"
                                   RepeatBehavior="Forever" />
                    </Storyboard>
               </BeginStoryboard>
     </EventTrigger>
</StackPanel.Triggers>

Private Sub backgroundWorker_DoWork(ByVal sender As Object, _
     ByVal e As DoWorkEventArgs) _
     Handles backgroundWorker.DoWork

     Dim result As String

     result = Me.SomeLongRunningMethodWPF()

     e.Result = result

End Sub

There are a few important things to note about DoWork. First, as soon as this method is entered, a new thread is spun off from the managed CLR threadpool. Next, it is important to remember that this is a secondary thread, so the same rules apply for not being able to update the UI controls which were created on the primary thread.

Remember, in our long running process, I noted that we were tracking progress? Specifically, every 100 iterations of the loop, we were calling:

VB
backgroundWorker.ReportProgress(i \ iteration)

The method ReportProgress is wired up to call the BackgroundWorker's ProcessChanged event.

VB
Private Sub backgroundWorker_ProgressChanged(ByVal sender As Object, _
     ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
     Handles backgroundWorker.ProgressChanged

     Me.wpfCount.Text = _
          CStr(e.ProgressPercentage) & "% processed."
End Sub

We are using this method to update a TextBlock with the current iteration count. Note that because this method runs on the Dispatcher thread, we can update the UI components freely. This is obviously not the most practical means of using the ProgressChanged event; however, I wanted to simply demonstrate its use. Once processing has completed in the DoWork method, the dispatcher thread’s RunWorkerCompleted method is called. This gives us an opportunity to handle the CompletedEventArgs.Result, which was passed in from DoWork.

VB
Private Sub backgroundWorker_RunWorkerCompleted(ByVal sender As Object, _
     ByVal e As RunWorkerCompletedEventArgs) _
     Handles backgroundWorker.RunWorkerCompleted

     wpfProgressBarAndText.Visibility = Windows.Visibility.Collapsed

     Me.wpfCount.Text = "Processing completed. " & _
          CStr(e.Result) & " rows processed."

     Me.myStoryboard.Stop(Me.lastStackPanel)

     Me.wpfAsynchronousStart.IsEnabled = True

End Sub

In the RunWorkerCompleted event, we first hide the progress bar and the progress bar status text since our long running operation has completed. We can also enable the Start button so the demo can be run again. As noted previously, we can access these UI elements here because we are back on the primary thread (Dispatcher thread).

The downloadable code which is available in both C# and VB also contains code which handles the CancelAsync method. This demonstrates how you can give the user the ability to cancel a long running process, should they decide it's not worth waiting for. In most applications, once the user starts a process, they are stuck waiting for it to complete. However, since this post has already run very long, I have decided to not include it here in the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I have been Software developer for the past 10 years. Mostly worked in web world in asp.net and c#. Recently began job using wpf and vb.net.

Also enjoy reading books on politics and American history, listening to music, and COD on XBox 360.

Visit my blog at: www.anothercodesite.com/blog

All posts have source code for both C# and VB.

Comments and Discussions

 
QuestionThanks! Pin
arceliaab14-Apr-15 2:55
arceliaab14-Apr-15 2:55 
QuestionWhy still so difficult? Pin
poesboes13-Feb-13 0:56
poesboes13-Feb-13 0:56 
GeneralThanks!! Pin
Member 852015928-May-12 12:24
Member 852015928-May-12 12:24 
QuestionCouple of typos need fixing Pin
galaxiom21-Dec-11 13:46
galaxiom21-Dec-11 13:46 
QuestionPassing Arguments for Asynchronous Process [modified] Pin
VSNetVbHarry28-Apr-11 9:34
VSNetVbHarry28-Apr-11 9:34 
GeneralExcellent article, excellent sample Pin
Snekemoi22-Oct-10 11:58
Snekemoi22-Oct-10 11:58 
GeneralExcellent explanation of Async on WPF Pin
turbohappy15-Oct-10 13:14
turbohappy15-Oct-10 13:14 
GeneralExcellent Explanation of Asynchronous delegates Pin
Viji Raj7-Jan-10 13:08
Viji Raj7-Jan-10 13:08 
GeneralFollow up article using WaitHandles [modified] Pin
johnsontroye23-Nov-08 13:43
johnsontroye23-Nov-08 13:43 
I wrote a follow up article on solving common problems when using WaitHandle class and WaitAll to block. The work around involves using a ManualResetEvent.

http://www.anothercodesite.com/Blog/post/2008/11/22/WaitHandle-Exceptions-and-Work-Arounds.aspx[^]

or

http://www.codeproject.com/KB/recipes/WaitHandleExceptions.aspx[^]


modified on Saturday, December 13, 2008 9:20 AM

GeneralAn alright explanation of Asynchronous delegates Pin
Sacha Barber11-Nov-08 4:42
Sacha Barber11-Nov-08 4:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.