The new Async feature in the Visual Studio Async CTP (SP1 Refresh) provides an elegantly simple technique to make code asynchronous.
Our writing team uses an internal app that would benefit from asynchronous calls. For each URL contained in the MSDN documentation that we publish, the app lists the title from the link, and the title parsed from HTML in the downloaded web page. We use the app to verify that URL links are valid.
The following example is a very simplified version of the relevant code, which does synchronous reads of multiple web pages.
Imports System.Net
Imports System.Threading.Tasks
Module Module1
Sub Main()
Dim urls As List(Of String) = BuildURLs()
Dim startTime = Date.Now
GetWebPagesSynchronous(urls)
‘GetWebPagesAsync(urls).Wait()
Dim seconds = (Date.Now – startTime).TotalSeconds
Console.WriteLine()
Console.WriteLine(“Ended in ” & seconds & ” seconds”)
Console.ReadKey()
End Sub
Private Sub GetWebPagesSynchronous(urls As List(Of String))
Dim client As New WebClient
For Each url In urls
Dim text = client.DownloadString(New Uri(url))
Console.WriteLine(GetTitle(text))
Next
End Sub
Private Function GetTitle(input As String) As String
Const startText = “<title>”
Const endText = “</title>”
Dim startIndex = input.IndexOf(startText)
If startIndex = -1 Then
Return “not found”
Else
startIndex += startText.Length
Dim endIndex = input.IndexOf(endText, startIndex)
Dim result = input.Substring(startIndex, endIndex – startIndex)
Return result.Trim()
End If
End Function
Private Function BuildURLs() As List(Of String)
Return New List(Of String) From
{
“http://www.microsoft.com”,
“http://msdn.com”
}
End Function
End Module
The following method transforms the above example to use the Async feature. Starting with the above example, change Main to call GetWebPagesAsync(urls).Wait() instead of GetWebPagesSynchronous(urls).
In your project, add a reference to AsyncCtpLibrary.dll, which is in My DocumentsMicrosoft Visual Studio Async CTPSamples.
Private Async Function GetWebPagesAsync(urls As List(Of String)) As Task
Dim theTasks As New List(Of Task(Of String))
For Each url In urls
Dim client As New WebClient()
Dim theTask As Task(Of String) =
client.DownloadStringTaskAsync(New Uri(url))
theTasks.Add(theTask)
Next
‘ Wait until the tasks are done.
‘ The Await statement causes execution to immediately return
‘ to the calling method, returning a new task. When all of the
‘ tasks complete, execution continues within this method.
‘ TaskEx.WhenAll would normally be Task.WhenAll.
‘ It’s TaskEx.WhenAll in the CTP only.
Await TaskEx.WhenAll(theTasks)
For Each theTask In theTasks
Console.WriteLine(GetTitle(theTask.Result))
Next
End Function
For each URL in the list, the DownloadStringTaskAsync method returns a Task object that is then stored in a generic List. The code waits for completion of all of the tasks by using an Await statement and the Task.WhenAll method. The WhenAll method accepts an object that implements IEnumerable(T), including List(T).
The code spins up all of these tasks without worrying about running out of resources from a language perspective. I was inclined to write code to carefully throttle the calls to conserve resources, but was told that is unnecessary.
On computer running Window s 7, at a randomly convenient time, I ran the above console apps five times synchronously and five times asynchronously, with 20 URLs and 100 URLs. For 20 URLs, the average time to complete was 7.2 seconds for synchronous and 3.2 seconds for Async. For 100 URLs, the average was 39.5 seconds for synchronous, and 28.5 seconds for Async. With 100 URLs on another day, the average was 39.9 seconds for synchronous, and 13.5 seconds for Async. The results are specific to the conditions, and your results will vary.
Note that the speedup in the Async example is almost entirely from the parallel processing, not the asynchronous processing. The advantages of asynchrony are that it does not tie up multiple threads, and that it does not tie up the user interface thread.
Cancellation
The following example adds cancellation. To actually cancel the operation, modify the code to change cancelIt = False to cancelIt = True, and change the Thread.Sleep call to specify the milliseconds before cancellation.
This code creates a CancellationTokenSource that contains a CancellationToken. The same cancellation token is passed to every call to DownloadStringTaskAsync. The CancellationTokenSource is also used to invoke cancellation.
Imports System.Net
Imports System.Threading
Imports System.Threading.Tasks
Module Module1
Sub Main()
Dim urls As List(Of String) = BuildURLs()
Dim startTime = Date.Now
ProcessAsyncCancellable(urls)
Dim seconds = (Date.Now – startTime).TotalSeconds
Console.WriteLine()
Console.WriteLine(“Ended in ” & seconds & ” seconds”)
Console.ReadKey()
End Sub
Private Sub ProcessAsyncCancellable(urls As List(Of String))
Dim cts As New CancellationTokenSource()
cts.Token.Register(Sub() Console.WriteLine(“cancelling”))
Dim theTask As Task = GetWebPagesAsync(urls, cts)
‘ To cancel midstream, set cancelIt to True.
Dim cancelIt = False
If cancelIt Then
‘ Set the milliseconds before cancelling.
Thread.Sleep(2000)
cts.Cancel()
Else
theTask.Wait()
End If
End Sub
Private Async Function GetWebPagesAsync( _
urls As List(Of String),
cts As CancellationTokenSource) As Task
Dim theTasks As New List(Of Task(Of String))
For Each webAddress In urls
Dim client As New WebClient()
Dim theTask As Task(Of String) =
client.DownloadStringTaskAsync(New Uri(webAddress), cts.Token)
theTasks.Add(theTask)
Next
Await TaskEx.WhenAll(theTasks)
For Each theTask In theTasks
Console.WriteLine(GetTitle(theTask.Result))
Next
End Function
Private Function BuildURLs() As List(Of String)
Return New List(Of String) From
{
“http://www.microsoft.com”,
“http://msdn.com”
}
End Function
Private Function GetTitle(input As String) As String
Const startText = “<title>”
Const endText = “</title>”
Dim startIndex = input.IndexOf(startText)
If startIndex = -1 Then
Return “not found”
Else
startIndex += startText.Length
Dim endIndex = input.IndexOf(endText, startIndex)
Dim result = input.Substring(startIndex, endIndex – startIndex)
&nbs
p; Return result.Trim()
End If
End Function
End Module
The above example displays all of the titles only when all of the tasks are complete. If you want to see the results before cancellation, you can replace GetWebPagesAsync with the following:
Private Async Function GetWebPagesAsync( _
urls As List(Of String),
cts As CancellationTokenSource) As Task
For Each webAddress In urls
Dim client As New WebClient()
Dim theTask As Task(Of String) =
client.DownloadStringTaskAsync(New Uri(webAddress), cts.Token)
Await theTask
Console.WriteLine(GetTitle(theTask.Result))
Next
End Function
Thanks to Anthony Green, Alex Turner, Lucian Wischik, Mick Alberts, and Thomas Petchel for providing tech review.
Resources
0 comments
Be the first to start the discussion.