Overview
In this pattern, callbacks achieve waiting without blocking. Language-based async support hides the callbacks.
await keyword
The await keyword suspends execution. It installs a callback by using continuation. The callback resumes the async method at the point of suspension.
When the async method is resumed:
- If operation completed successfully and was a
Task<TResult>, thenTResultis returned. - If awaited task ended in
Canceledstate, anOperationCanceledExceptionis thrown. - If awaited task ended in
Faultedstate, the exception that caused the fault is thrown.- If multiple exceptions caused the fault, the
Task.Exceptionproperty holds anAggregateExceptionof all errors.
- If multiple exceptions caused the fault, the
- When awaiting a
Task, the await expression is of typevoid. - When awaiting a
Task<TResult>, the await expression is of typeTResult.
If a SynchronizationContext is associated with the thread that was executing the async method when it was suspended, the async method
resumes on that same synchronization context using that context’s Post method. Otherwise, it relies on the task scheduler that was current
at the time of suspension. The task scheduler decides whether the awaited async operation should resume where it completed or whether the
resumption should be scheduled.
Suspension and resumption can be configured:
- With
Task.Yieldto introduce a yield point in the async methodpublic class Task : … { public static YieldAwaitable Yield(); … } - With
Task.ConfigureAwaitto inform the await operation not to capture and resume on the context, but to continue execution wherever the async operation that was being awaited completed:await someTask.ConfigureAwait(continueOnCapturedContext:false);
cancellation
Cancellation tokens are created with CancellationTokenSource objects. The CTS’s Token property returns the cancellation token that is signaled when the CTS’s Cancel method is called:
var cts = new CancellationTokenSource();
string result = await DownloadStringTaskAsync(url, cts.Token);
… // at some point later, potentially on another thread
cts.Cancel();
A single token can cancel multiple asynchronous invocations.
Pass CancellationToken.None to a method to indicate that cancellation will never be requested. The method will then optimize.
monitoring progress
Some async methods expose progress through a progress interface passed into the method.
Built-in Task-based Combinators
Use these methods for composing and working with tasks.
Task.Run
The Task.Run(Func<Task>) overload allows you to use await within the offloaded work. This is equivalent to using TaskFactory.StartNew and Unwrap in the Task Parallel Library:
public async void button1_Click(object sender, EventArgs e)
{
pictureBox1.Image = await Task.Run(async() =>
{
using(Bitmap bmp1 = `await DownloadFirstImageAsync()`)
using(Bitmap bmp2 = `await DownloadSecondImageAsync()`)
return Mashup(bmp1, bmp2);
});
}
Task.FromResult
Use when data may already be available and just needs to be returned from a task-returning method:
public Task<int> GetValueAsync(string key)
{
int cachedValue;
return TryGetCachedValue(out cachedValue) ? Task.FromResult(cachedValue) : GetValueAsyncInternal();
}
private async Task<int> GetValueAsyncInternal(string key) { … }
Task.WhenAll
Use to asynchronously wait on multiple tasks:
Task [] asyncOps = (from addr in addrs select SendMailAsync(addr)).ToArray();
try
{
await Task.WhenAll(asyncOps);
}
catch(Exception exc)
{
foreach(Task faulted in asyncOps.Where(t => t.IsFaulted))
{
… // work with faulted and faulted.Exception
}
}
Task.WhenAny
Use when you need just one of multiple async operations to complete.
Task.WhenAny Use Case — Redundancy
Perform an operation multiple times and select the one that completes first:
var recommendations = new List<Task<bool>>()
{
GetBuyRecommendation1Async(symbol),
GetBuyRecommendation2Async(symbol),
GetBuyRecommendation3Async(symbol)
};
while (recommendations.Count > 0)
{
Task<bool> recommendation = await Task.WhenAny(recommendations);
try
{
if (await recommendation) BuyStock(symbol);
break;
}
catch(WebException exc)
{
recommendations.Remove(recommendation);
}
}
Task.WhenAny Use Case — Interleaving
Launching multiple operations and waiting for them all to complete, but processing them as they finish:
List<Task<Bitmap>> imageTasks = (from imageUrl in urls select GetBitmapAsync(imageUrl)).ToList();
while (imageTasks.Count > 0) // Loop until no imageTasks remain
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks); // When one finishes…
imageTasks.Remove(imageTask); // …remove it from the list.
Bitmap image = await imageTask;
panel.AddImage(image);
}
catch
{
}
}
Task.WhenAny Use Case - Throttling [
Documentation]
Task.WhenAny Use Case - Early Bailout [
Documentation]
Task.Delay
Use to introduce pauses into an async method’s execution.
Use in combination with Task.WhenAny to implement a time-out:
public async void btnDownload_Click(object sender, EventArgs e)
{
btnDownload.Enabled = false;
try
{
Task<Bitmap> download = GetBitmapAsync(url);
if (download == await Task.WhenAny(download, Task.Delay(3000)))
{
Bitmap bmp = await download;
pictureBox.Image = bmp;
status.Text = "Downloaded";
}
else
{
pictureBox.Image = null;
status.Text = "Timed out";
var ignored = download.ContinueWith(t => Trace("Task finally completed"));
}
}
finally { btnDownload.Enabled = true; }
}
Custom Task-based Combinators
RetryOnFault Helper Method
Retry an operation if the previous attempt fails:
public static async Task<T> RetryOnFault<T>(Func<Task<T>> function, int maxTries)
{
for (int i=0; i<maxTries; i++)
{
try
{
return await function().ConfigureAwait(false);
}
catch
{
if (i == maxTries - 1)
throw;
}
}
return default(T);
}
NeedOnlyOne Helper Method
Launch multiple operations, wait for any of them, then cancel the rest:
public static async Task<T> NeedOnlyOne(params Func<CancellationToken,Task<T>> [] functions)
{
var cts = new CancellationTokenSource();
var tasks = (from function in functions
select function(cts.Token)).ToArray();
var completed = await Task.WhenAny(tasks).ConfigureAwait(false);
cts.Cancel();
foreach (var task in tasks)
{
var ignored = task.ContinueWith(t => Log(t), TaskContinuationOptions.OnlyOnFaulted);
}
return completed;
}
Use the method:
double currentPrice = await NeedOnlyOne(
ct => GetCurrentPriceFromServer1Async("msft", ct),
ct => GetCurrentPriceFromServer2Async("msft", ct),
ct => GetCurrentPriceFromServer3Async("msft", ct));
WhenAllOrFirstException Helper Method
Wait for all tasks to complete, unless one faults, then stop waiting immediately:
public static Task<T[]> WhenAllOrFirstException<T>(IEnumerable<Task<T>> tasks)
{
var inputs = tasks.ToList();
var ce = new CountdownEvent(inputs.Count);
var tcs = new TaskCompletionSource<T[]>();
Action<Task> onCompleted = (Task completed) =>
{
if (completed.IsFaulted)
tcs.TrySetException(completed.Exception.InnerExceptions);
if (ce.Signal() && !tcs.Task.IsCompleted)
tcs.TrySetResult(inputs.Select(t => t.Result).ToArray());
};
foreach (var t in inputs) t.ContinueWith(onCompleted);
return tcs.Task;
}
Building Task-Based Data Structures
Example custom task-based data structures include AsyncCache and AsyncProducerConsumerCollection.