Async/Await in a Console Application
Here’s the situation, you want to be able to build a console application and still take advantage of the asynchronous programming model available in the latest versions of VB (2012/2013).
No matter the reason behind why you may want to do this, the answer I give will still be the same; however, I’ll provide my real-world reasoning behind my desire to have Async
/Await
from a Console Application… TESTING. Many times it is a lot easier to do testing of API’s from a Console Application; however, if your API’s are Async
/Await
… thus the problem.
To do so, you may be tempted to start by modifying the Sub Main() based on the tons of examples you’ve seen for several other types of projects.
1
2
3
4
5
Async Sub Main()
Dim result = Await WebUtilities.FetchUrlAsync("http://addressof.com")
Console.WriteLine(result)
Console.ReadLine()
End Function
This, however, will not work. Visual Studio will quickly alert you to this situation and you are left scratching your head. You then will proceed (most likely) to search the web for a possible solution. There are several out there that state that you need to setup some sort of synchronization context or some such and that you’ll need to either use one that someone else has made or build one yourself. There sure seems like there’s got to be a simpler solution…
1
2
3
4
5
6
7
8
9
Sub Main()
Task.Run(AddressOf MainAsync).Wait()
End Sub
Async Function MainAsync() As Task
Dim result = Await WebUtilities.FetchUrlAsync("http://addressof.com")
Console.WriteLine(result)
Console.ReadLine()
End Function
Hmmm… that seems to do the trick.
The Task.Run
method has several overloads, one of which takes the address of an Async
/Await
style method. This actually kicks off the process of running that method asynchronously; however, if left to just calling this Run()
method, the application would then exit immediately as nothing is keeping the Main method from completing. To work around this problem, the Wait()
method is executed upon the return of the Run()
method.
To keep things straight, I named the target method MainAsync()
. This is where I will place all of my code that would have normally been in the original Main()
method. From this point, anything I want to test; whether it be Async
/Await
or not, it “just works”.
A couple of side notes:
It does have to be a function, but since I’m not actually needing to return a “result”, we will be returning Task
. This is necessary for the Async
/Await
functionality to work. The rule is either to return Task(Of [type])
or just Task
. Whenever you add the Async
keyword to an existing Sub in other project types, this is only allowed on events and only in projects that have an understanding of this concept. There is a ton of behind the scenes compiler magic that occurs and the only place where this should be done is where it’s been “blessed” by the platform/compiler teams. For all code that you write, stick to Function with either a result of Task
or Task(Of [type])
; keep it simple.
The return of the Run
method occurs as soon as the first Await
is encountered in the target method; so in the above example, it happens immediately. The Wait()
method will pause the execution of the Main()
method until the total completion of the target method.