Comparing Synchronous and Asynchronous Behavior

Synchronous code executes in the order in which it occurs in the compiled program. Asynchronous code executes out of order. When an asynchronous call occurs, the call is made to the asynchronous invoke method and immediately returns. The flow of execution resumes while the asynchronous process finishes separately. You can write code to block until the asynchronous process finishes, or you can pass a callback method to the asynchronous process so it will notify you by invoking the callback when the data is ready.

The GcdDemo.sln file demonstrates code that determines the greatest common divisor of two numbers . The sample application calculates the greatest common divisor using recursion, demonstrating both synchronous and asynchronous calls to the Calculate method. If you step through the asynchronous calls, you will see that the code does not appear to go to the asynchronously invoked method at the point at which it is called. By placing a breakpoint in the asynchronous worker method, you can see the worker thread that is spun up to process the asynchronous code. Open the Threads window when the asynchronous method's breakpoint is reached.

Programming Synchronous Behavior

Listing 6.2 demonstrates a synchronous call to the Calculate method on lines 4 through 13. The Calculate method employs recursion to calculate the greatest common divisor. When we invoke Calculate synchronously on lines 19 and 20 with the Invoke method, we use a delegate to manage the invocation. The signature of the delegate simply needs to match the signature of the method we want to invoke.

Listing 6.2 A Synchronous Call to a Recursive Method
 1:  Private Delegate Function MyDelegate(ByVal Numerator As Long, _ 2:    ByVal Denominator As Long) As Long 3: 4:  Private Function Calculate(ByVal Numerator As Long, _ 5:    ByVal Denominator As Long) As Long 6: 7:    If (Numerator Mod Denominator = 0) Then 8:      Return Denominator 9:    Else 10:     Return Calculate(Denominator, Numerator Mod Denominator) 11:   End If 12: 13: End Function 14: 15: Private Sub MenuItem5_Click(ByVal sender As System.Object, _ 16:   ByVal e As System.EventArgs) Handles MenuItem5.Click 17: 18:   SetResult(Numerator, Denominator, _ 19:     Invoke(New MyDelegate(AddressOf Calculate), _ 20:     New Object() {Numerator, Denominator})) 21: 22: End Sub 23: 24: Private Sub SetResult(ByVal Numerator As Long, _ 25:   ByVal Denominator As Long, ByVal Gcd As Long) 26: 27:   Const mask As String = _ 28:     "{0}. The greatest common divisor of {1} and {2} is {3}" 29: 30:   Static I As Integer 31:   I += 1 32: 33:   ListBox1.Items.Insert(0, _ 34:     String.Format(mask, I, Numerator, Denominator, Gcd)) 35: End Sub 

When we call the Invoke method (lines 19 and 20) we pass the address of the method as a delegate and the parameters to that method as an array of objects; this happens on line 20. Invoke returns the value of the Calculate method, which is the greatest common divisor Gcd . Numerator , Denominator , and Gcd are passed to SetResult to write formatted output.

Technically you do not have to call Invoke to call the Calculate method in this example. In this instance there is no reason to use Invoke to call Calculate other than to demonstrate the Invoke method. However, you will need to use the Invoke method if the calling thread is different than the thread of the Windows control with which you want to interact. For example, suppose you wanted to calculate the greatest common divisor on a separate thread and display the value in a Windows Forms control. Then you could use Invoke to interact with the Windows Forms control. As described, Invoke would marshal the data from the Gcd thread onto the same thread as the one containing the Windows control. Using Invoke to marshal data onto a control's thread is a safe way to use multithreading in Windows Forms applications.

Programming Asynchronous Behavior

The asynchronous method BeginInvoke is employed syntactically the same as the Invoke method. You pass the method to call using a delegate and the arguments to the method using an array of type Object . The delegate plays the role of work to perform, and BeginInvoke creates a separate worker thread to complete the task represented by the delegate. Listing 6.3 demonstrates the BeginInvoke method.

Listing 6.3 An Asynchronous Method Invocation
 1:  Private Sub SetResult(ByVal Numerator As Long, _ 2:    ByVal Denominator As Long, ByVal Gcd As Long) 3: 4:    Const mask As String = _ 5:      "{0}. The greatest common divisor of {1} and {2} is {3}" 6: 7:    Static I As Integer 8:    I += 1 9: 10:   ListBox1.Items.Insert(0, _ 11:     String.Format(mask, I, Numerator, Denominator, Gcd)) 12: End Sub 13: 14: Private Delegate Sub AsynchDelegate(ByVal Numerator As Long, _ 15:   ByVal Denominator As Long) 16: 17: Private Sub AsynchCalculate(ByVal Numerator As Long, _ 18:   ByVal Denominator As Long) 19: 20:   SetResult(Numerator, Denominator, _ 21:     Calculate(Numerator, Denominator)) 22: 23: End Sub 24: 25: Private Sub MenuItem6_Click(ByVal sender As System.Object, _ 26:   ByVal e As System.EventArgs) Handles MenuItem6.Click 27: 28:   BeginInvoke(New AsynchDelegate(AddressOf AsynchCalculate), _ 29:     New Object() {Numerator, Denominator}) 30: 31: End Sub 

The biggest difference between Listings 6.2 and 6.3 is that we call BeginInvoke in line 28 of Listing 6.3. I defined a slightly new delegate that represents the work to complete asynchronously. AsynchCalculate calls SetResult , passing Numerator , Denominator , and the result of the Calculate method. The calculated greatest common divisor is the same; the difference is that we can keep interacting with the graphical user interface during calculation.

TIP

The Invoke, BeginInvoke, EndInvoke , and CreateGraphics control methods are safe to call from any thread.

Completing Asynchronous Calls

Our example in Listing 6.3 calls a subroutine, AsynchCalculate . Because we are not expecting any data back from AsynchCalculate , we can let the asynchronous method finish without further attention. However, if you are expecting information back from an asynchronous call, you will want to handle the completion of the asynchronous process.

There are four ways to complete an asynchronous call. You can poll the IAsyncResult.IsCompleted property, block with EndInvoke , use a Wait Handle , or employ a delegate. The first three ways are discussed here; Chapter 3 covers delegates.

The BeginInvoke method returns an object that implements the IAsyncResult interface. You can poll IsCompleted or request a WaitHandle from the IAsyncResult object. If you pass a delegate to BeginInvoke , you can complete the call there, as we did in Listing 6.3. Finally, you can call EndInvoke to request the data after the asynchronous call has finished, or call EndInvoke immediately. EndInvoke will block until the asynchronous call finishes.

Polling IAsyncResult.IsCompleted

IAsyncResult.IsCompleted returns a Boolean, and you use it like any other Boolean value. Listing 6.4 demonstrates how to call the Calculate method from GcdDemo.sln , wait until IsCompleted is True , and then obtain the value with EndInvoke .

Listing 6.4 Polling IAsyncResult.IsCompleted
 Dim Result As IAsyncResult = BeginInvoke( _   New MyDelegate(AddressOf Calculate), New Object() {532, 16}) While (Not Result.IsCompleted)   Application.DoEvents() End While SetResult(532, 26, EndInvoke(Result)) 

The While loop uses IsCompleted to poll. We can add work in between the While and End While statements or just wait until IsCompleted is True . In our example, we call Application.DoEvents to allow the Windows message queue to clear out; otherwise the delegate containing the Calculate method won't have time to process. Finally, when the loop terminates, we can call EndInvoke to obtain the result, as shown.

Blocking with EndInvoke

In Listing 6.4 we could take out the While loop and achieve approximately the same effect. The statement containing EndInvoke would block until the asynchronous result is ready. Of course, if we cut out the While loop, there would be no opportunity to do additional work. Alternatively, we could insert lines of code between BeginInvoke and EndInvoke to get some work done asynchronously and then block. This is simulated in Listing 6.5; the comment is where we would place our lines of code.

Listing 6.5 Blocking with EndInvoke
 Dim Result As IAsyncResult = BeginInvoke( _   New MyDelegate(AddressOf Calculate), New Object() {532, 16}) ' Insert some code to perform some work here SetResult(532, 26, EndInvoke(Result)) 
Using WaitHandle Objects

Thus far we have completed asynchronous calls using delegates, polling with IAsyncResult.IsCompleted , and blocking with EndInvoke . The last way I'll demonstrate here is to block using a WaitHandle .

The IAsyncResult.AsyncWaitHandle property permits you to specifically define the wait conditions for the block. You can call WaitHandle.WaitOne , WaitHandle.WaitAll , or WaitHandle.WaitAny to block, passing IAsyncResult.AsyncWaitHandle to these methods.

Use the WaitHandle class for things like processing several asynchronous requests and then waiting until they all finish, waiting for any of several requests to finish, or waiting a specific amount of time and setting a timeout if the request hasn't finished. Listing 6.6 demonstrates how we can send several pairs of integers to the greatest common divisor calculator and then wait until the calculations for all the pairs are finished.

Listing 6.6 Blocking by Using a WaitHandle
 Dim Result As IAsyncResult = _   BeginInvoke(New MyDelegate(AddressOf Calculate), _   New Object() {652, 18}) Result.AsyncWaitHandle.WaitOne(5000, False) SetResult(652, 18, EndInvoke(Result)) 

Listing 6.6 blocks for up to five seconds (represented by the 5,000 milliseconds passed to WaitOne ) waiting for the asynchronous call to Calculate to return.

You can explore more about the WaitHandle class in the System. Threading namespace help documentation.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net