Home > Articles > Programming > Visual Basic

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

Implementing Task-Based Asynchrony

As you remember from Chapter 41, the Task class provides methods and other members that enable you to execute CPU-intensive work, by splitting code across all the available processors so that most of the code is executed concurrently, when possible. Such members of the Task class return instances of the Task class itself, and therefore can be used along with Await. This possibility has some advantages:

  • You can execute synchronous code on a separate thread more easily.
  • You can run multiple tasks concurrently and wait for them to complete before making further manipulations.
  • You can use Await with CPU-consuming code.

This approach is known as Task-Based Asynchrony, and in this section you learn how to get the most out of it.

Switching Threads

In Chapter 40 you learned how to write code that can run on a separate thread, how to create new threads manually, and how to use the Thread Pool managed by the .NET Framework. With this approach, you run a portion of synchronous code in a separate thread, thus keeping the caller thread-free from an intensive and potentially blocking work. In the .NET Framework 4.6, you have additional alternatives to reach the same objective but writing simpler code. The Task.Run method enables you to run a new task asynchronously, queuing such a task into a thread in the Thread Pool. The result is returned as Task handle for the intensive work, so that you can use Await to wait for the result. Task.Run takes as the first argument a delegate that defines the work that will be executed in the background thread. Such a delegate can be represented either by a method that you point to via the AddressOf clause or by lambdas. In the latter case, the delegate can be a System.Action represented by a statement lambda or a System.Func(Of T) represented by a lambda expression. The following example demonstrates how synchronous code is easily executed in a separate thread by invoking Task.Run:

Private Async Sub RunIntensiveWorkAsync()
    'This runs on the UI thread
    Console.WriteLine("Starting...")

    'This runs on a Thread Pool thread
    Dim result As Integer = Await Task.Run(Function()
                                               Dim workResult As Integer = _
                                                   SimulateIntensiveWork()
                                               Return workResult
                                           End Function)

    'This runs again on the UI thread
    Console.WriteLine("Finished")
    Console.ReadLine()
End Sub

Private Function SimulateIntensiveWork() As Integer
    Dim delay As Integer = 5000
    Threading.Thread.Sleep(delay)
    Return delay
End Function

While the result of Task.Run is being awaited, the control is immediately returned to the user interface, which remains responsive in the Console window. All the Console.WriteLine and Console.ReadLine statements are executed on the UI thread, whereas the simulated CPU-consuming code runs on the separate thread. Task.Run schedules a new task exactly as Task.Factory.StartNew does; you saw this method in Chapter 41. So, this code has the same effect as using Task.Run:

Dim result As Integer = Await Task.Factory.StartNew(Function()
                                                       Dim workResult _
                                                       As Integer = _
                                                           SimulateIntensiveWork()
                                                       Return workResult
                                                   End Function)

In summary, Task.Run lets you easily execute intensive computations on a separate thread, taking all the benefits of Await.

Using Combinators

The Task class has other interesting usages, such as managing concurrent operations. This is possible because of two methods, Task.WhenAll and Task.WhenAny, also known as combinators. Task.WhenAll creates a task that will complete when all the supplied tasks complete; Task.WhenAny creates a task that will complete when at least one of the supplied tasks completes. For example, imagine you want to download multiple RSS feeds information from a website. Instead of using Await against individual tasks to complete, you can use Task.WhenAll to continue only after all tasks have completed. The following code provides an example of concurrent download of RSS feeds from the Microsoft Channel 9 feed used previously:

Private Async Sub DownloadAllFeedsAsync()
    Dim feeds As New List(Of Uri) From
        {New Uri("http://channel9.msdn.com/Tags/windows+8/RSS"),
        New Uri("http://channel9.msdn.com/Tags/windows+phone"),
        New Uri("http://channel9.msdn.com/Tags/visual+basic/RSS")}

    'This task completes when all of the requests complete
    Dim feedCompleted As IEnumerable(Of String) = _
                                               Await Task.
                                               WhenAll(From feed In feeds
                                               Select New System.Net.WebClient().
                                               DownloadStringTaskAsync(feed))

    'Additional work here...
End Sub

This code creates a collection of tasks by sending a DownloadStringTaskAsync request for each feed address in the list of feeds. The task completes (and thus the result of awaiting WhenAll is returned) only when all three feeds have been downloaded, meaning that the complete download result will not be available if only one or two feeds have been downloaded. WhenAny works differently because it creates a task that completes when any of the tasks in a collection of tasks completes. The following code demonstrates how to rewrite the previous example using WhenAny:

Private Async Sub DownloadFeedsAsync()
    Dim feeds As New List(Of Uri) From
        {New Uri("http://channel9.msdn.com/Tags/windows+8/RSS"),
        New Uri("http://channel9.msdn.com/Tags/windows+phone"),
        New Uri("http://channel9.msdn.com/Tags/visual+basic/RSS")}

    'This task completes when any of the requests complete
    Dim feedCompleted As Task(Of String) = Await Task.WhenAny(From feed In feeds
                                                Select New System.Net.WebClient().
                                                DownloadStringTaskAsync(feed))
    'Additional work here...
End Sub

In this case a single result will be yielded because the task will be completed when any of the tasks completes. You can also wait for a list of tasks defined as explicit asynchronous methods, like in the following example:

Public Async Sub WhenAnyRedundancyAsync()

    Dim messages As New List(Of Task(Of String)) From
        {
            GetMessage1Async(),
            GetMessage2Async(),
            GetMessage3Async()
        }
    Dim message = Await Task.WhenAny(messages)
    Console.WriteLine(message.Result)
    Console.ReadLine()
End Sub

Public Async Function GetMessage1Async() As Task(Of String)
    Await Task.Delay(700)
    Return "Hi VB guys!"
End Function

Public Async Function GetMessage2Async() As Task(Of String)
    Await Task.Delay(600)
    Return "Hi C# guys!"
End Function

Public Async Function GetMessage3Async() As Task(Of String)
    Await Task.Delay(500)
    Return "Hi F# guys!"
End Function

Here you have three asynchronous methods, each returning a string. The code builds a list of tasks including each asynchronous method in the list. Task.WhenAny receives the instance of the collection of tasks as an argument and completes when one of the three methods completes. In this example, you are also seeing for the first time the Task.Delay method. This is the asynchronous equivalent of Thread.Sleep, but while the latter blocks the thread for the specified number of milliseconds, with Task.Delay the thread remains responsive.

  • + Share This
  • 🔖 Save To Your Account