- Background
- Basics
- Concepts
- Examples
- Introduced in C# 5
- Manual thread management (
Thread
,BackgroundWorker
)
- Task-based Asynchronous Pattern (TAP)
- Added
Task
andTask<T>
classes (called awaitables, there are others...) - Added
async
andawait
keywords - Separates tasks from threads -- better runtime performance
// no return
public async Task MethodWithNoReturnAsync()
{
await GetUrlAsStringAsync("http://google.com");
}
// return string
public async Task<string> MethodWithReturnAsync()
{
return await GetUrlAsStringAsync("http://google.com");
}
Async calls typically follow a few rules:
- return an awaitable (ie,
Task
orTask<TResult>
) - awaitables are await'ed
- marked with the
async
keyword - end with Async
Hot tip
Naming async methods with the suffix Async
is a common pattern and used to be the norm. These days it's less common and you probably don't want to do it. Whatever you choose, be consistent.
TAP decouples tasks from threads. Many tasks can execute on a single thread, without blocking.
public async Task<string> FooAsync(string url)
{
var task = MethodWithReturnAsync();
// do something here ...
// task may, or may not, have completed
return await task;
}
public async Task<string> AnotherMethodAsync()
{
var result = await GetUrlAsStringAsync("http://google.com");
// return the generic type, not Task<string>
// can seem odd at first
return result.ToUpper();
}
public Task DoNothingReallyQuicklyAsync()
{
// no await, no async
// returning 1 is convention
return Task.FromResult(1);
}
public Task<string> ToUpper(string value) // no async!
{
return Task.FromResult(value.ToUpper());
}
public async Task Caller()
{
await DoNothingReallyQuicklyAsync();
var result = await ToUpper("the string");
}
Hot Tip
In the above situation, you can mark the method with async, however you should never do this. Marking the method with async causes the runtime to instantiate the async machinery for the method, which will have significant performance overhead. If you don’t need this, it is best to avoid it.
In general, if you can avoid calling await
and use Task.FromResult
you should.
You can return void
from an async
method. Doesn't mean you should.
private async void Handler(object o, Event e)
{
var result = await FooAsync();
DisplayFoo(damageResult);
}
fooButton.Clicked += Handler;
Hot Tip
Returning void from an async
method has a number of problems. You have no ability to interact with the task after running it: waiting for completion, cancelling, progress etc.
Secondly, the asynchronous programming model uses the awaitable to return any exceptions that occurred. This is what enables calling methods to catch exceptions thrown by asynchronous code. Returning void
means that the caller will never see the exception.
private async void ThrowExceptionAsync()
{
await Task.Run(_ => throw new InvalidOperationException());
}
public void WillNeverCatchException()
{
try
{
// Cannot await 'void'
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught
throw;
}
}
Hot Tip
Don't ignore compiler warnings about missing await
calls. If you really, really mean it, use a pragma to hide the warning.
public async Task<User> GetUser(int userId)
{
return await db.GetUserAsync(userId);
}
public static Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUser(id));
}
return await Task.WhenAll(getUserTasks);
// alternative ...
return await Task.WhenAny(getUserTasks);
}
Hot Tip
Be aware of LINQ’s lazy evaluation when mixing with async code. Typically you will want to materialise the list (with ToList or ToArray) before waiting!
Use configureAwait()
to improve performance -- if you don't need to be on the UI / Request thread, use configureAwait(false)
.
From Stephen Cleary’s Blog:
private async Task DownloadFileAsync(string fileName)
{
var fileContents = await DownloadFileContentsAsyn(fileName)
.ConfigureAwait(false);
// Because of the ConfigureAwait(false), we are no longer on the original context
// - unning on the thread pool
await WriteToDiskAsync(fileName, fileContents)
.ConfigureAwait(false);
// Second call to ConfigureAwait(false) is not *required*
// - good practice (and less error prone!)
}
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
// Asynchronously wait
// - UI thread is not blocked
await DownloadFileAsync(fileNameTextBox.Text);
// Resume on the UI context
// - safe to directly access UI elements.
resultTextBox.Text = "File downloaded!";
}
Hot Tip
Each level of async call has it’s own context. Once you have called configureAwait(false)
you will be running on a ThreadPool context, but it’s good practice to still use configureAwait(false)
for each subsequent await
for clarity.
Gotcha If the Task is already complete, then the context isn’t captured, meaning you may still be on the UI thread, even after calling configureAwait(false)
.
async Task MyMethodAsync()
{
// original context
await Task.FromResult(1);
// original context
await Task.FromResult(1).ConfigureAwait(continueOnCapturedContext: false);
// original context
var random = new Random();
int delay = random.Next(2); // Delay is either 0 or 1
await Task.Delay(delay).ConfigureAwait(continueOnCapturedContext: false);
// might or might not run in the original context
// - true when you await any Task that might complete very quickly
}
private DamageResult CalculateDamageDone()
{
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
Old | New | Description |
---|---|---|
task.Wait | await task | Wait/await for a task to complete |
task.Result | await task | Get the result of a completed task |
Task.WaitAny | await Task.WhenAny | Wait/await for one of a collection of tasks to complete |
Task.WaitAll | await Task.WhenAll | Wait/await for every one of a collection of tasks to complete |
Thread.Sleep | await Task.Delay | Wait/await for a period of time |
Microsoft Introduction to async / await (C#) Microsoft In-Depth doc on async c/ await
Six Essential Tips for Async | Channel 9
Stephen Cleary’s Blog - Async and Await
Best Practices in Async / Await
Would like to see a "more in depth" for how to create custom awaiters, especially since this goes into how blocking tasks can impact performance because eg. One waits for some external event.
Resources: https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern
https://devblogs.microsoft.com/pfxteam/await-anything/
Sadly I cannot seem to find a somewhat proper resource for the more fun discipline of implementing a custom awaitable class that would get returned by GetAwaiter.. But there is also that, allowing to await eg. User events easily