The other day, someone posted some code about how to make Async
/Await
easier to work with. What they went for was not correct, and looked more like RXs model of OnNext
/OnError
/OnCompleted
. The reason it was wrong is that it did not support awaiting.
As a rule, any Async
code you write WILL need synchronizing at some point, so must be awaitable. This is the reason why async void
is a very very dodgy thing and should only be used for event handlers, but lets not even get into that.
Now when I was talking to the chap who wrote the original stuff, I pointed out a more typical use case would have been to do something like this:
class Program
{
static void Main(string[] args)
{
new Program().Backup();
Console.ReadLine();
}
public async void Backup()
{
var backupStatus = await BackupAsync();
}
public async Task<string> BackupAsync()
{
return await Task.Run(() => "groovy");
}
}
Another eagle eyed reader pointed out that you should never use async void
(I was doing that to be as much in line with the original poster's code), and that the only reason my code worked was due to the use of the Console.ReadLine()
which was blocking the main thread.
I did know that I should never really use async void
, so I set about trying to post a better version of this. One important note here is that I am using a ConsoleApplication
. So I tried this:
class Program
{
private static async void Main(string[] args)
{
await new Program().Backup();
}
public async Task Backup()
{
var backupStatus = await BackupAsync();
await Task.Delay(5000);
Console.WriteLine(backupStatus);
}
public async Task<string> BackupAsync()
{
return await Task.Run(() => "groovy");
}
}
The compiler complained about this, and would not allow an async void main
method. This is the error.
‘NonAwaitingConsoleApplication.Program.Main(string[])’: an entry point cannot be marked with the
‘async’ modifier
Ok, so how about this one then:
class Program
{
private static void Main(string[] args)
{
Task.Run(() => MainAsync(args)).Wait();
}
static async void MainAsync(string[] args)
{
Console.WriteLine(DateTime.Now.ToLongTimeString());
await new Program().Backup();
Console.WriteLine(DateTime.Now.ToLongTimeString());
}
public async Task Backup()
{
var backupStatus = await BackupAsync();
await Task.Delay(5000);
Console.WriteLine(backupStatus);
}
public async Task<string> BackupAsync()
{
return await Task.Run(() => "groovy");
}
}
And that exited straight away…mmmmm. Interesting. The reason for this is that there is no SynchronizationContext
in a ConsoleApplication
. So the thread is simply returned to the OS, and the app exits prematurely. Not quite what we are after. What can we do?
Well, luckily there is a very clever chap (who I highly rate) called Stephen Cleary, who has written a nice set of Extensions which is also written by Stephen Toub (he knows his onions for sure), so I have full trust in this library. It is called NitoAsyncEx. It is also available via NuGet: Nito.AsyncEx
Anyway, with this in place, we can now create an Awaiting ConsoleApplication
like this:
internal class Program
{
private static void Main(string[] args)
{
AsyncContext.Run(() => MainAsync(args));
}
static async void MainAsync(string[] args)
{
Console.WriteLine(DateTime.Now.ToLongTimeString());
await new Program().Backup();
Console.WriteLine(DateTime.Now.ToLongTimeString());
}
public async Task Backup()
{
var backupStatus = await BackupAsync();
await Task.Delay(5000);
Console.WriteLine(backupStatus);
}
public async Task<string> BackupAsync()
{
return await Task.Run(() => "groovy");
}
}
Notice the use of the AsyncContext
that is the NitoAsyncEx magic class that makes it all possible. I urge you to have a look inside that class (you can do so using Reflector or via the Codeplex site source tab), and see how it works. Stephen is doing quite a bit on your behalf, like ensuring there is a valid SynchronizationContext
. Please have a look at it. In fact, look at it all it's a very useful library
Which when run produces the following output, as expected, then exits (as expected).