At the API level, the way to achieve waiting without blocking is to provide callbacks. For Tasks, this is achieved through methods like ContinueWith. Language-based asynchrony support hides callbacks by allowing asynchronous operations to be awaited within normal control flow, with compiler-generated code targeting this same API-level support.
In .NET 4.5, C# and Visual Basic directly support asynchronously awaiting Task and Task, with the ‘await’ keyword in C# and the ‘Await’ keyword in Visual Basic. If awaiting a Task, the await expression is of type void; if awaiting a Task, the await expression is of type TResult. An await expression must occur inside the body of an asynchronous method, with return type void, Task, or Task for some TResult. For more information on the C# and Visual Basic language support in the .NET Framework 4.5, see the C# and Visual Basic language specifications.
Under the covers, the await functionality installs a callback on the task via a continuation. This callback will resume the asynchronous method at the point of suspension. When the asynchronous method is resumed, if the awaited operation completed successfully and was a Task, its TResult will be returned. If the Task or Task awaited ended in the Canceled state, an OperationCanceledException will be thrown. If the Task or Task awaited ended in the Faulted state, the exception that caused it to fault will be thrown. It is possible for a Task to fault due to multiple exceptions, in which case only one of these exceptions will be propagated; however, the Task’s Exception property will return an AggregateException containing all of the errors.
If a SynchronizationContext is associated with the thread executing the asynchronous method at the time of suspension (e.g. SynchronizationContext.Current is non-null), the resumption of the asynchronous method will take place on that same SynchronizationContext through usage of the context’s Post method. Otherwise, it will rely on whatever System.Threading.Tasks.TaskScheduler was current at the time of suspension (typically this will be TaskScheduler.Default, which targets the .NET ThreadPool). It’s up to this TaskScheduler whether to allow the resumption to execute wherever the awaited asynchronous operation completed or to force the resumption to be scheduled. The default scheduler will typically allow the continuation to run on whatever thread the awaited operation completed.
When called, an asynchronous method synchronously executes the body of the function up until the first await expression on an awaitable instance that is not yet completed, at which point the invocation returns to the caller. If the asynchronous method does not return void, a Task or Task is returned to represent the ongoing computation. In a non-void asynchronous method, if a return statement is encountered, or the end of the method body is reached, the task is completed in the RanToCompletion final state. If an unhandled exception causes control to leave the body of the asynchronous method, the task ends in the Faulted state (if that exception is an OperationCanceledException, the task instead ends in the Canceled state). In this manner, the result or exception will eventually be published.
There are several important variations from the described behavior. For performance reasons, if a task has already completed by the time the task is awaited, control will not be yielded, and the function will instead continue executing. Additionally, it is not always the desired behavior to return back to the original context; method-level support is provided to change this behavior, and this is described in more detail later in this document.
Yield and ConfigureAwait
Several members give more control over an asynchronous method’s execution. The Task class provides a Yield method that may be used to introduce a yield point into the asynchronous method.
public class Task : …
public static YieldAwaitable Yield();
This is equivalent to asynchronously posting or scheduling back to whatever context is current.
The Task class also provides a ConfigureAwait method which gives more control over how suspension and resumption occur in an asynchronous method. As mentioned previously, by default the current context at the time an async method is suspended is captured, and that captured context is used to invoke the async method’s continuation upon resumption. In many cases, this is the exact behavior you want. However, in some cases you don’t care where you end up, and as a result you can achieve better performance by avoiding such posts back to the original context. To enable this, ConfigureAwait can be used to inform the await operation not to capture and resume on the context, instead preferring to continue execution wherever the asynchronous operation being awaited completed:
TAP methods that are cancelable expose at least one overload that accepts a CancellationToken, a type introduced to System.Threading in .NET 4.
A CancellationToken is created through a CancellationTokenSource. The source’s Token property returns the CancellationToken that will be signaled when the source’s Cancel method is invoked. For example, consider downloading a single Web page and wanting to be able to cancel the operation. We create a CancellationTokenSource, pass its token to the TAP method, and later potentially call the source’s Cancel method:
var cts = new CancellationTokenSource();
string result = await DownloadStringAsync(url, cts.Token);
Cancellation requests may be initiated from any thread.
CancellationToken.None may be passed to any method accepting a CancellationToken in order to indicate that cancellation will never be requested. The callee will find that the cancellationToken’s CanBeCanceled will return false, and the callee can optimize accordingly. (For testing purposes, a pre-canceled CancellationToken may also be passed in, constructed using CancellationToken’s constructor that accepts a Boolean value to indicate whether the token should start in an already-canceled or not-cancelable state.)
The same CancellationToken may be handed out to any number of asynchronous and synchronous operations. This is one of the strong suits of the CancellationToken approach: cancellation may be requested of synchronous method invocations, and the same cancellation request may be proliferated to any number of listeners. Another benefit of this approach is that the developer of the asynchronous API is in complete control of whether cancellation may be requested and of when cancellation may take effect, and the consumer of the API may selectively determine to which of multiple asynchronous invocations cancellation requests will be propagated.