Getting started with async-await
Remarks#
async-await allows asynchronous (means non-blocking, parallel) execution of code. It helps to keep your UI responsive at all times, while running potentially long operations in the background.
It is especially useful for I/O operations (like downloading from a server, or reading a file from the HDD), but it can also be used to execute CPU intensive calculations without freezing your application.
simple usage
Three things are needed to use async-await:
- The
Taskobject: This object is returned by a method which is executed asynchronously. It allows you to control the execution of the method. - The
awaitkeyword: “Awaits” aTask. Put this keyword before theTaskto asynchronously wait for it to finish - The
asynckeyword: All methods which use theawaitkeyword have to be marked asasync
A small example which demonstrates the usage of this keywords
public async Task DoStuffAsync()
{
var result = await DownloadFromWebpageAsync(); //calls method and waits till execution finished
var task = WriteTextAsync(@"temp.txt", result); //starts saving the string to a file, continues execution right await
Debug.Write("this is executed parallel with WriteTextAsync!"); //executed parallel with WriteTextAsync!
await task; //wait for WriteTextAsync to finish execution
}
private async Task<string> DownloadFromWebpageAsync()
{
using (var client = new WebClient())
{
return await client.DownloadStringTaskAsync(new Uri("https://stackoverflow.com"));
}
}
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath, FileMode.Append))
{
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
}
} Some things to note:
- You can specify a return value from an asynchronous operations with
Task<string>or similar. Theawaitkeyword waits till the execution of the method finishes and returns thestring. - the
Taskobject simply contains the status of the execution of the method, it can be used as any other variable. - if an exception is thrown (for example by the
WebClient) it bubbles up at the first time theawaitkeyword is used (in this example at the linevar result (...)) - It is recommended to name methods which return the
Taskobject asMethodNameAsync
execute synchronous code asynchronous
If you want to execute synchronous code asynchronous (for example CPU extensive calculations), you can use Task.Run(() => {}).
public async Task DoStuffAsync()
{
await DoCpuBoundWorkAsync();
}
private async Task DoCpuBoundWorkAsync()
{
await Task.Run(() =>
{
for (long i = 0; i < Int32.MaxValue; i++)
{
i = i ^ 2;
}
});
}the Task object
The Task object is an object like any other if you take away the async-await keywords.
Consider this example:
public async Task DoStuffAsync()
{
await WaitAsync();
await WaitDirectlyAsync();
}
private async Task WaitAsync()
{
await Task.Delay(1000);
}
private Task WaitDirectlyAsync()
{
return Task.Delay(1000);
}The difference between this two methods is simple:
WaitAsyncwait forTask.Delayto finish, and then returns.WaitDirectlyAsyncdoes not wait, and just returns theTaskobject instantly.
Every time you use the await keyword, the compiler generates code to deal with it (and the Task object it awaits).
- On calling
await WaitAsync()this happens twice: once in the calling method, and once in the method itself. - On calling
await WaitDirectlyAsyncthis happens only once (in the calling method). You would therefore archive a little speedup compared withawait WaitAsync().
Careful with exceptions: Exceptions will bubble up the first time a Task is awaited. Example:
private async Task WaitAsync()
{
try
{
await Task.Delay(1000);
}
catch (Exception ex)
{
//this might execute
throw;
}
}
private Task WaitDirectlyAsync()
{
try
{
return Task.Delay(1000);
}
catch (Exception ex)
{
//this code will never execute!
throw;
}
}async void
You can use void (instead of Task) as a return type of an asynchronous method. This will result in a “fire-and-forget” action:
public void DoStuff()
{
FireAndForgetAsync();
}
private async void FireAndForgetAsync()
{
await Task.Delay(1000);
throw new Exception(); //will be swallowed
}As you are returning void, you can not await FireAndForgetAsync. You will not be able to know when the method finishes, and any exception raised inside the async void method will be swallowed.