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.
![asynchronous-Demo-in-vb.net.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 event handler for the button click of the
second demo.
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(AddressOf Me.SomeLongRunningSynchronousMethod)
caller.Begin(Invoke(1000000000, AddressOf CallbackMethod, Nothing))
End Sub
Notice 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 Begin Invoke. Begin
Invoke 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 delegate method had in their signature. We do the same with Begin
Invoke; however there are two additional parameters passed which are not seen in
the delegate or delegate method signature. The two additional parameters are
Delegate Callback 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 Begin Invoke call.
Essentially there are multiple ways to handle asynchronous execution using Begin
Invoke. The values passed for these parameters depend on which technique is
used. Some of these techniques include:
- Call Begin Invoke, do some processing, call
End Invoke.
- Using a Wait Handle of type IAsyncResult
returned by Begin Invoke.
- Polling using the Is Completed property of
the IAsyncResult returned by Begin Invoke.
- 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 Begin Invoke to fire off the long running method on a
new thread. Begin Invoke 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 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 our call to Begin Invoke passes an integer which is required by the
delegate and 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 we are
setting the text and visibility property of the visual Indicator Text Block
here. We can access this control because this method is called on the primary
thread which is also where these controls were created.
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 Begin Invoke) so that we can call End Inoke on it and get the
results of the long running method. End Invoke will always block further
processing until Begin Invoke completes. However, we don't need to worry about
that because we are in the callback method which only fires when Begin Invoke
has already completed.
Once End Invoke 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 End Invoke is called the Sub Update UI is called and is passed the return
value from End Invoke. Also notice this method is wrapped in a try catch block.
It is considered good coding standards to always call End Invoke 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 Begin Invoke completed
without any exceptions.
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 Update UI method. It takes as a parameter the return value from End
Invoke 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 Update UI parameter into string. This will be eventually be passed into
Begin Invoke.
Next you will see the call to Invoke. We could have also used a call to Begin
Invoke 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
the call to Invoke is run off Me. Dispatcher. The Dispatcher in WPF is the
thread manager for your application. In order for the 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 Begin Invoke or the
synchronous method Invoke as we have done off the dispatcher.
Finally Sub UpdateUIIndicators takes the results passed into it and updates a
Text Block on the UI. It also changes the text on another Text Block to indicate
processing has completed.
We have now successfully written a responsive multi-threaded WPF application. We
have done it using Delegates, Begin Invoke, End Invoke, 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
![asynchronous-Event-Based-Demo-in-vb.net.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. Background Worker (Background Worker) 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 tracking process, and cancellation with the same level
of simplicity.
Consider the following method which we have decided to spin off on a separate
thread so the UI can remain responsive.
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) AndAlsobackgroundWorker.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 background
Worker. Report Progress method.
Using the Background Worker and the event driven model the first thing we need
to do is create an instance of the Background Worker. There are two ways to
accomplish this task:
Create the Background Worker instance declaratively in your code.
Create the Background Worker in your XAML markup as a resource. Using this
method allows you to wire up your event method handles using attributes.