ASYNCHRONOUS METHODS

Normally a program calls a routine and control passes to that routine. When the routine finishes executing, control returns to the calling code, which resumes executing its own code. All of this happens synchronously so the calling code waits until the called routine finishes all of its work before it continues.

Visual Basic provides several methods that you can use to execute code asynchronously. In those cases a calling piece of code can launch a routine in a separate thread and continue executing before the routine finishes. If your computer has multiple cores or CPUs, the calling code and the asynchronous routine may both be able to execute simultaneously on separate processors, potentially saving a lot of time.

Visual Basic provides several methods of various difficulties for executing code asynchronously. The following sections describe three of the more manageable approaches.

Calling EndInvoke Directly

This method uses a delegate’s BeginInvoke method to start a routine executing asynchronously. Later the code calls EndInvoke to wait for the routine to finish and to process the result.

To use this method, first define a delegate that represents the routine that you want to run asynchronously. Call the delegate’s BeginInvoke method, passing it whatever parameters the method needs plus two additional parameters: a callback method and a parameter to pass to the callback method. For this technique, set the extra parameters to Nothing so the routine does not invoke a callback when it completes. (The following section explains how to use the callback.)

The call to BeginInvoke launches the asynchronous code on its own thread and then returns immediately so the calling code can perform other tasks.

After the calling code has done as much as it can before the asynchronous thread finishes, it should invoke the delegate’s EndInvoke method. That method waits until the asynchronous thread finishes (if it isn’t already finished) and returns the result of the original method.


NOTE
It is important that the code call EndInvoke even if the thread is executing a subroutine rather than a function and the code doesn’t care about any returned result. The call to EndInvoke lets the program free resources used by the asynchronous thread.

The AsyncCallEndInvoke example program, which is shown in Figure 16-1 and available for download on the book’s website, uses this approach to generate embossed images for four different pictures.

FIGURE 16-1: The AsyncCallEndInvoke example program generates embossed images asynchronously.

image

The program uses an extension method named Emboss that allows a Bitmap object to return an embossed version of itself. The details of that method aren’t important for this discussion so its code is not shown here. Download the example program to see how it works. The only feature of that method that is important right now is that it takes a long time to finish so running on multiple threads can make the program faster.

The following code shows how the AsyncCallEndInvoke program defines the delegate it uses to launch the Emboss extension method:

Private Delegate Function EmbossDelegate(bm As Bitmap) As Bitmap

The Emboss method takes a Bitmap as a parameter (the object that is calling the extension method) and returns a new Bitmap so the delegate takes a Bitmap as a parameter and returns a Bitmap as a result.

The following code shows how the program invokes the Emboss extension method asynchronously:

' Emboss the images asynchronously.
Private Sub btnAsync_Click(sender As Object, e As EventArgs) _
 Handles btnAsync.Click
    lblElapsedTime.Text = ""
    DisplayOriginalImages()
    Me.Cursor = Cursors.WaitCursor
    Application.DoEvents()
 
    Dim start_time As Date = Now
 
    ' Get all of the bitmaps.
    Dim bm1 As Bitmap = My.Resources.JackOLanterns
    Dim bm2 As Bitmap = My.Resources.Dunk
    Dim bm3 As Bitmap = My.Resources.Flatirons
    Dim bm4 As Bitmap = My.Resources.world
 
    ' Start the processes.
    Dim caller1 As EmbossDelegate = AddressOf bm1.Emboss
    Dim result1 As IAsyncResult =
        caller1.BeginInvoke(bm1, Nothing, Nothing)
 
    Dim caller2 As EmbossDelegate = AddressOf bm2.Emboss
    Dim result2 As IAsyncResult =
        caller2.BeginInvoke(bm2, Nothing, Nothing)
 
    Dim caller3 As EmbossDelegate = AddressOf bm3.Emboss
    Dim result3 As IAsyncResult =
        caller3.BeginInvoke(bm3, Nothing, Nothing)
 
    Dim caller4 As EmbossDelegate = AddressOf bm4.Emboss
    Dim result4 As IAsyncResult =
        caller4.BeginInvoke(bm4, Nothing, Nothing)
 
    ' Wait for the processes to complete.
    PictureBox1.Image = caller1.EndInvoke(result1)
    PictureBox2.Image = caller2.EndInvoke(result2)
    PictureBox3.Image = caller3.EndInvoke(result3)
    PictureBox4.Image = caller4.EndInvoke(result4)
 
    ' Display the elapsed time.
    Dim stop_time As Date = Now
    Dim elapsed_time As TimeSpan = stop_time - start_time
    lblElapsedTime.Text = elapsed_time.TotalSeconds.ToString("0.00") & " seconds"
    Me.Cursor = Cursors.Default
End Sub

After some preliminaries such as displaying the original images on the form and saving the start time, the program loads four bitmaps from its resources. Then for each bitmap it creates an EmbossDelegate object that refers to the bitmap’s instance of the Emboss extension method and calls that delegate’s BeginInvoke method. At that point the method can begin executing asynchronously but the main program’s code continues executing.

After it has called BeginInvoke for all four delegates, the program needs the results of the asynchronous methods so it calls EndInvoke for all four delegates. It passes EndInvoke the IAsyncResult object that it received when it called BeginInvoke to give the method information about the asynchronous call. EndInvoke returns the result of the function that the delegate represents, in this case the embossed images.

For example, the first delegate, named caller1, refers to the first bitmap’s version of the Emboss extension method bm1.Emboss. That delegate’s EndInvoke method returns the value returned by bm1.Emboss, which is an embossed version of the bitmap bm1.

The program assigns the returned bitmaps to the PictureBoxes’ Image properties and displays the elapsed time.

In one set of tests on my dual-core computer, creating the four embossed images took roughly 12.9 seconds synchronously but only 7.2 seconds asynchronously. Because the computer has two cores, you might expect the asynchronous version to take only half the time used by the synchronous version, but there is some overhead in setting up and coordinating the threads. The result is still an impressive reduction in time, however, and would be even greater on a computer with more cores.

Handling a Callback

The technique described in the previous section directly calls EndInvoke to make the main UI thread wait until its asynchronous threads have finished before the main program continues.

Another approach is to let the main program continue without waiting for the threads to complete and then have the threads invoke a callback method when they finish.

This approach lets the main program ignore the asynchronous threads for most purposes but it does make the flow of execution less predictable. While the threads are running, the user can do other things, perhaps even starting new threads that duplicate those that are already running. When a thread finishes, the callback routine executes, possibly interrupting whatever the user is doing at the time.

There’s one important catch to working with callbacks: Only the thread that created the user interface (called the UI thread) can directly interact with the controls in the user interface. That means the asynchronous threads cannot directly assign images to PictureBoxes, display text in Labels or TextBoxes, move controls around, or otherwise manipulate the controls. Because the threads invoke the callback methods, those methods cannot directly interact with the controls, either. In this example that means the callback methods cannot directly assign the PictureBox’s Image properties.

You can get around this restriction by using the form’s Invoke method. Invoke executes one of the form’s methods on the UI thread.

The AsyncHandleCallback example program, which is available for download on the book’s website, is similar to the AsyncCallEndInvoke example program but it uses callbacks instead of calling EndInvoke in the program’s main flow of execution.

The AsyncHandleCallback program defines the Emboss delegate just as the AsyncCallEndInvoke program does. The following code shows how the program calls BeginInvoke for its first image:

Dim caller1 As EmbossDelegate = AddressOf bm1.Emboss
Dim result1 As IAsyncResult =
    caller1.BeginInvoke(bm1, AddressOf AsyncCallback, PictureBox1)

The code makes a delegate named caller1 that represents the bm1 object’s Emboss method. It calls the delegate’s BeginInvoke method, passing it the bitmap to process (bm1), the address of the callback routine (AsyncCallback), and the PictureBox that should display the embossed result (PictureBox1).

The code performs similar steps for the other images and then the btnAsync_Click event handler that contains this code ends without waiting for the threads to finish.

Later, when a thread finishes, it invokes the following callback routine:

' Handle a callback.
Private Sub AsyncCallback(result As AsyncResult)
    Dim caller As EmbossDelegate =
        DirectCast(result.AsyncDelegate, EmbossDelegate)
 
    ' Get the parameter we passed to the callback.
    Dim pic As PictureBox = DirectCast(result.AsyncState, PictureBox)
 
    ' Get the method's return value.
    Dim bm As Bitmap = caller.EndInvoke(result)
 
    ' Use Invoke to display the image on the PictureBox.
    Dim displayer As New SetPictureBoxImageDelegate(AddressOf SetPictureboxImage)
    Me.Invoke(displayer, pic, bm)
End Sub

This code receives as a parameter an AsyncResult object representing the thread’s result. It uses that result’s AsyncDelegate property to get a reference to the original delegate that the program used to call BeginInvoke.

The result’s AsyncState property holds whatever value the program passed as the final parameter to BeginInvoke. In this example, that was the PictureBox that should display the embossed image. The callback code converts the AsyncState property into a PictureBox.

The code then calls the delegate’s EndInvoke method and saves the result, which is the embossed bitmap created by the thread.

Because this code is executing in an asynchronous thread, it cannot directly set the PictureBox’s Image property so it uses Invoke to run the SetPictureBoxImage method on the UI thread. To do that, it makes a delegate variable pointing to the method and then calls Invoke, passing it the delegate and the parameters to pass to the SetPictureBoxImage method.

The following code shows the definition of the SetPictureBoxImageDelegate and the SetPictureBoxImage method:

' Set a PictureBox's Image property.
Private Delegate Sub SetPictureBoxImageDelegate(pic As PictureBox, img As Image)
Private Sub SetPictureBoxImage(pic As PictureBox, img As Image)
    pic.Image = img
End Sub

This method simply sets the PictureBox’s Image property. (I’ve removed some code that displays the elapsed time to keep the method simple. Download the example to see how that works.)


IGNORING INVOKE
Actually this program seems to work even if the callback sets the PictureBoxes’ Image properties directly, but messing with controls from non-UI threads is a bad habit and doesn’t work with all properties. Try setting the PictureBox’s BorderStyle property to None directly in the callback and in subroutine SetPictureBoxImage to see what happens.

For more information on calling methods asynchronously by using BeginInvoke and EndInvoke, see the article “Calling Synchronous Methods Asynchronously” at http://msdn.microsoft.com/library/2e08f6yc.aspx.

Using Async and Await

Calling EndInvoke directly in the UI thread makes the code relatively simple but it means the program is blocked until all of the asynchronous threads finish running. Using a callback allows the main UI thread to finish before the threads do so the UI can interact with the user, but the code is somewhat more complex, particularly if the callback must manipulate controls, so it needs to use the form’s Invoke method.

Visual Basic 2012 provides two new keywords that make it easier to use the callback approach without actually writing callbacks and calling Invoke yourself.

The Async keyword indicates that a routine may have parts that should run asynchronously. You should apply this keyword to your event handlers and other routines that will start tasks asynchronously and then wait for them.

The Await keyword makes the program wait until a particular task has finished running asynchronously. When it sees the Await keyword, Visual Basic essentially converts the rest of the routine into a callback that it invokes when the task has finished. One really nice feature of that “virtual callback” is that it executes on the UI thread so it can manipulate controls directly without using the form’s Invoke method.

The AsyncAwait example program, which is available for download on the book’s website, is very similar to the AsyncCallEndInvoke and AsyncHandleCallback example programs but it uses the Async and Await keywords.

The following code shows the btnAsync_Click event handler that executes when you click the program’s Async button:

' Emboss the images asynchronously.
Private Async Sub btnAsync_Click(sender As Object, e As EventArgs) _
 Handles btnAsync.Click
    lblElapsedTime.Text = ""
    DisplayOriginalImages()
    Me.Cursor = Cursors.WaitCursor
    Application.DoEvents()
 
    Dim start_time As Date = Now
 
    ' Get all of the bitmaps.
    Dim bm1 As Bitmap = My.Resources.JackOLanterns
    Dim bm2 As Bitmap = My.Resources.Dunk
    Dim bm3 As Bitmap = My.Resources.Flatirons
    Dim bm4 As Bitmap = My.Resources.world
 
    'Start four embossing tasks running.
    Dim task1 As New Task(Of Bitmap)(AddressOf bm1.Emboss)
    task1.Start()
    Dim task2 As New Task(Of Bitmap)(AddressOf bm2.Emboss)
    task2.Start()
    Dim task3 As New Task(Of Bitmap)(AddressOf bm3.Emboss)
    task3.Start()
    Dim task4 As New Task(Of Bitmap)(AddressOf bm4.Emboss)
    task4.Start()
 
    ' Wait for the tasks to finish.
    PictureBox1.Image = Await task1
    PictureBox2.Image = Await task2
    PictureBox3.Image = Await task3
    PictureBox4.Image = Await task4
 
    ' Display the elapsed time.
    Dim stop_time As Date = Now
    Dim elapsed_time As TimeSpan = stop_time - start_time
    lblElapsedTime.Text = elapsed_time.TotalSeconds.ToString("0.00") & " seconds"
    Me.Cursor = Cursors.Default
End Sub

Because this event handler has parts that run asynchronously, its declaration includes the Async keyword.

The code begins as the previous versions do, saving the start time and retrieving the program’s bitmaps. It then creates Task objects to make the embossed images on asynchronous threads. The Of Bitmap part of the Task declarations means that the Tasks return Bitmaps. For each Bitmap, the program creates a Task to execute the Bitmap’s Emboss method and calls the Task’s Start method to make it start running on its own thread.

After it has created and launched all four Tasks, the program calls Await for each Task. Each Task returns a Bitmap and the program displays the Bitmap in the corresponding PictureBox.

Calling Await is very similar to calling EndInvoke directly except that behind the scenes Visual Basic moves the code that follows into a callback so execution does not block until the Tasks return. The btnAsync_Click event handler blocks until the Tasks finish, but the program’s control returns to the event loop so the program can perform other tasks such as responding to the user. This is similar to the way control returns to the AsyncHandleCallback program’s main code while the asynchronous threads continue executing.

When the Tasks finish, they invoke a behind-the-scenes callback that continues executing the btnAsync_Click event handler.

The result is a combination of the results of the two previous examples. As in program AsyncCallEndInvoke, the button’s event handler doesn’t finish until all of the Tasks have completed so you can write that code in a fairly linear fashion without worrying about callbacks. However, the program actually is using a callback behind the scenes so the button’s event handler doesn’t block the entire application while it is running. (And you don’t need to create any callbacks yourself.)

To see the difference, run the example program and click the Async button to start building the embossed images. After one or two of the images are displayed, click the Reset button to display the original images. Repeat the same steps in the other two examples to see the differences.

For more information about using Async and Await, see the article “Asynchronous Programming with Async and Await” at http://msdn.microsoft.com/library/hh191443(v=vs.110).aspx.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.186.219