From time to time, I receive questions from developers which highlight either a need for more information about the new “async” and “await” keywords in C# and Visual Basic. I’ve been cataloguing these questions, and I thought I’d take this opportunity to share my answers to them.

Conceptual Overview

Where can I get a good overview of the async/await keywords?

Generally, you can find lots of resources (links to articles, videos, blogs, etc.) on the Visual Studio Async page at http://msdn.com/async. To call out just a few specific resources, the October 2011 issue of MSDN Magazine included a trio of articles that provided a good introduction to the topic. If you read them all, I recommend you read them in the following order:

  1. Asynchronous Programming: Easier Asynchronous Programming with the New Visual Studio Async CTP
  2. Asynchronous Programming: Pause and Play with Await
  3. Asynchronous Programming: Understanding the Costs of Async and Await

The .NET team blog also includes a good overview of asynchrony in .NET 4.5: Async in 4.5: Worth the Await.

Why do I need the compiler to help me with asynchronous programming?

Anders Hejlsberg’s Future directions for C# and Visual Basic talk at //BUILD/ provides a great tour through why a compiler is really beneficial here. In short, the compiler takes on the responsibility of doing the kind of complicated transformations you’d otherwise be doing by hand as you manually invert your control flow using callbacks and continuation-passing style. You get to write your code using the language’s control flow constructs, just as you would if you were writing synchronous code, and the compiler under the covers applies the transformations necessary to use callbacks in order to avoid blocking threads.

To achieve the benefits of asynchrony, can’t I just wrap my synchronous methods in calls to Task.Run?

It depends on your goals for why you want to invoke the methods asynchronously. If your goal is simply to offload the work you’re doing to another thread, so as to, for example, maintain the responsiveness of your UI thread, then sure. If your goal is to help with scalability, then no, just wrapping a synchronous call in a Task.Run won’t help. For more information, see Should I expose asynchronous wrappers for synchronous methods? And if from your UI thread you want to offload work to a worker thread, and you use Task.Run to do so, you often typically want to do some work back on the UI thread once that background work is done, and these language features make that kind of coordination easy and seamless.

The “Async” Keyword

What does the “async” keyword do when applied to a method?

When you mark a method with the “async” keyword, you’re really telling the compiler two things:

  1. You’re telling the compiler that you want to be able to use the “await” keyword inside the method (you can use the await keyword if and only if the method or lambda it’s in is marked as async). In doing so, you’re telling the compiler to compile the method using a state machine, such that the method will be able to suspend and then resume asynchronously at await points.
  2. You’re telling the compiler to “lift” the result of the method or any exceptions that may occur into the return type. For a method that returns Task or Task<TResult>, this means that any returned value or exception that goes unhandled within the method is stored into the result task. For a method that returns void, this means that any exceptions are propagated to the caller’s context via whatever “SynchronizationContext” was current at the time of the method’s initial invocation.

Does using the “async” keyword on a method force all invocations of that method to be asynchronous?

No. When you invoke a method marked as “async”, it begins running synchronously on the curren thread. So, if you have a synchronous method that returns void and all you do to change it is mark it as “async”, invocations of that method will still run synchronously. This is true regardless of whether you leave the return type as “void” or change it to “Task”. Similarly, if you have a synchronous method that returns some TResult, and all you do is mark it as “async” and change the return type to be “Task<TResult>”, invocations of that method will still run synchronously.

Marking a method as “async” does not affect whether the method runs to completion synchronously or asynchronously. Rather, it enables the method to be split into multiple pieces, some of which may run asynchronously, such that the method may complete asynchronously. The boundaries of these pieces can occur only where you explicitly code one using the “await” keyword, so if “await” isn’t used at all in a method’s code, there will only be one piece, and since that piece will start running synchronously, it (and the whole method with it) will complete synchronously.

Does the “async” keyword cause the invocation of a method to queue to the ThreadPool? To create a new thread? To launch a rocket ship to Mars?

No. No. And no. See the previous questions. The “async” keyword indicates to the compiler that “await” may be used inside of the method, such that the method may suspend at an await point and have its execution resumed asynchronously when the awaited instance completes. This is why the compiler issues a warning if there are no “awaits” inside of a method marked as “async”.

Can I mark any method as “async”?

No. Only methods that return void, Task, or Task<TResult> can be marked as async. Further, not all such methods can be marked as “async”. For example, you can’t use “async”:

  • On your application’s entry point method, e.g. Main. When you await an instance that’s not yet completed, execution returns to the caller of the method. In the case of Main, this would return out of Main, effectively ending the program.
  • On a method attributed with:
    • [MethodImpl(MethodImplOptions.Synchronized)]. For a discussion of why this is disallowed, see What’s New for Parallelism in .NET 4.5 Beta; attributing a method as Synchronized is akin to wrapping the entire body of the method with lock/SyncLock.
    • [SecurityCritical] and [SecuritySafeCritical]. When you compile an async method, the implementation / body of the method actually ends up in a compiler-generated MoveNext method, but the attributes for it remain on the signature you defined. That means that attributes like [SecuritySafeCritical] (which is meant to have a direct impact on what you’re able to do in the body of the method) would not work correctly, and thus they’re prohibited, at least for now.
  • On a method with ref or out parameters.  The caller would expect those values to be set when the synchronous invocation of the method completes, but the implementation might not set them until its asynchronous completion much later.
  • On a lambda used as an expression tree.  Async lambda expressions cannot be converted to expression trees.

Are there any conventions I should use when writing methods marked as “async”?

Yes. The Task-based Asynchronous Pattern (TAP) is entirely focused on how asynchronous methods that return Task or Task<TResult> should be exposed from libraries. This includes, but is not limited to, methods implemented using the “async” and “await” keywords. For an in-depth tour through the TAP, see the Task-based Asynchronous Pattern document.

Do I need to “Start” Tasks created by methods marked as “async”?

No.  Tasks returned from TAP methods are “hot”, meaning the tasks represent operations that are already in-progress.  Not only do you not need to call “.Start()” on such tasks, but doing so will fail if you try.  For more details, see FAQ on Task.Start.

Do I need to “Dispose” Tasks created by methods marked as “async”?

No. In general, you don’t need to Dispose of any tasks.  See Do I need to dispose of Tasks?.

How does “async” relate to the current SynchronizationContext?

For methods marked as “async” that return Task or Task<TResult>, there is no method-level interaction with the SynchronizationContext. However, for methods marked as “async” that return void, there is a potential interaction.

When an “async void” method is invoked, the prolog for the method’s invocation (as handled by the AsyncVoidMethodBuilder that is created by the compiler to represent the method’s lifetime) will capture the current SynchronizationContext (“capture” here means it accesses it and stores it). If there is a non-null SynchronizationContext, two things will be affected:

  • The beginning of the method’s invocation will result in a call to the captured context’s OperationStarted method, and the completion of the method’s execution (whether synchronous or asynchronous) will result in a call to that captured context’s OperationCompleted method. This gives the context a chance to reference count outstanding asynchronous operations; if instead the method had returned a Task or Task<TResult>, the caller could have done the same tracking via that returned task.
  • If the method completes due to an unhandled exception, the throwing of that exception will be Post’d to the captured SynchronizationContext. This gives the context a chance to deal with the failure. This is in contrast to a Task or Task<TResult>-returning async method, where the exception can be marshaled to the caller through the returned task.

If there isn’t a SynchronizationContext when the “async void” method is called, no context is captured, and then as there are no OperationStarted / OperationCompleted methods to call, none are invoked. In such a case, if an exception goes unhandled, the exception is propagated on the ThreadPool, which with default behavior will cause the process to be terminated.

The “Await” Keyword

What does the “await” keyword do?

The “await” keyword tells the compiler to insert a possible suspension/resumption point into a method marked as “async”.

Logically this means that when you write “await someObject;” the compiler will generate code that checks whether the operation represented by someObject has already completed. If it has, execution continues synchronously over the await point. If it hasn’t, the generated code will hook up a continuation delegate to the awaited object such that when the represented operation completes, that continuation delegate will be invoked. This continuation delegate will re-enter the method, picking up at this await location where the previous invocation left off. At this point, regardless of whether the awaited object had already completed by the time it was awaited, any result from the object will be extracted, or if the operation failed, any exception that occurred will be propagated.

In code, this means that when you write:

await someObject;

the compiler translates that into something like the following (this code is an approximation of what the compiler actually generates):

private class FooAsyncStateMachine : IAsyncStateMachine {     // Member fields for preserving “locals” and other necessary state     int $state;     TaskAwaiter $awaiter;     …     public void MoveNext()     {         // Jump table to get back to the right statement upon resumption         switch (this.$state)         {             …             case 2: goto Label2;             …         }         …         // Expansion of “await someObject;”         this.$awaiter = someObject.GetAwaiter();         if (!this.$awaiter.IsCompleted)         {             this.$state = 2;             this.$awaiter.OnCompleted(MoveNext);             return;             Label2:         }         this.$awaiter.GetResult();         …     } }

What are awaitables? What are awaiters?

While Task and Task<TResult> are two types very commonly awaited, they’re not the only ones that may be awaited.

An “awaitable” is any type that exposes a GetAwaiter method which returns a valid “awaiter”. This GetAwaiter method may be an instance method (as it is in the case of Task and Task<TResult>), or it may be an extension method.

An “awaiter” is any type returned from an awaitable’s GetAwaiter method and that conforms to a particular pattern. The awaiter must implement the System.Runtime.CompilerServices.INotifyCompletion interface, and optionally may implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface. In addition to providing an implementation of the OnCompleted method that comes from INotifyCompletion (and optionally the UnsafeOnCompleted method that comes from ICriticalNotifyCompletion), an awaiter must also provide an IsCompleted Boolean property, as well as a parameterless GetResult method. GetResult returns void if the awaitable represents a void-returning operation, or it returns a TResult if the awaitable represents a TResult-returning operation.

Any type that follows the awaitable pattern may be awaited. For a discussion of several approaches to implementing custom awaitables, see await anything;. You can also implement awaitables customized for very specific situations: for some examples, see Advanced APM Consumption in Async Methods and Awaiting Socket Operations.

Where can’t I use “await”?

You can’t use await:

Is “await task;” the same thing as “task.Wait()”?

No.

“task.Wait()” is a synchronous, potentially blocking call: it will not return to the caller of Wait() until the task has entered a final state, meaning that it’s completed in the RanToCompletion, Faulted, or Canceled state. In contrast, “await task;” tells the compiler to insert a potential suspension/resumption point into a method marked as “async”, such that if the task has not yet completed when it’s awaited, the async method should return to its caller, and its execution should resume when and only when the awaited task completes. Using “task.Wait()” when “await task;” would have been more appropriate can lead to unresponsive applications and deadlocks; see Await, and UI, and deadlocks! Oh my!.

There are some other potential pitfalls to be aware of when using “async” and “await”. For some examples, see:

Is there a functional difference between “task.Result” and “task.GetAwaiter().GetResult()”?

Yes, but only if the task completes non-successfully.  If the task ends in the RanToCompletion state, these are completely equivalent statements.  If, however, the task ends in the Faulted or Canceled state, the former will propagate the one or more exceptions wrapped in AggregateException, while the latter will propagate the exception directly (and if there are more than one in the task, it’ll just propagate one of them).  For background on why this difference exists, see Task Exception Handling in .NET 4.5.

How does “await” relate to the current SynchronizationContext?

This is entirely up to the type being awaited. For a given await, the compiler generates code that ends up calling an awaiter’s OnCompleted method, passing in the continuation delegate to be executed. The compiler-generated code knows nothing about SynchronizationContext, and simply relies on the awaited object’s OnCompleted method to invoke the provided callback when the awaited operation completes. It’s the OnCompleted method, then, that’s responsible for making sure that the delegate is invoked in the “right place,” where “right place” is left entirely up to the awaiter.

The default behavior for awaiting a task (as implemented by the TaskAwaiter and TaskAwaiter<TResult> types returned from Task’s and Task<TResult>’s GetAwaiter methods, respectively) is to capture the current SynchronizationContext before suspending, and then when the awaited task completes, if there had been a current SynchronizationContext that got captured, to Post the invocation of the continuation delegate back to that SynchronizationContext. So, for example, if you use “await task;” on the UI thread of your application, OnCompleted when invoked will see a non-null current SynchronizationContext, and when the task completes, it’ll use that UI’s SynchronizationContext to marshal the invocation of the continuation delegate back to the UI thread.

If there isn’t a current SynchronizationContext when you await a Task, then the system will check to see if there’s a current TaskScheduler, and if there is, the continuation will be scheduled to that when the task completes.

If there isn’t such a context or scheduler to force the continuation back to, or if you do “await task.ConfigureAwait(false)” instead of just “await task;”, then the continuation won’t be forced back to the original context and will be allowed to run wherever the system deems appropriate. This typically means either running the continuation synchronously wherever the awaited task completes or running the continuation on the ThreadPool.

Can I use “await” in console apps?

Sure. You can’t use “await” inside of your Main method, however, as entry points can’t be marked as async. Instead, you can use “await” in other methods in your console app, and then if you call those methods from Main, you can synchronously wait (rather than asynchronously wait) for them to complete, e.g.

public static void Main() {     FooAsync().Wait(); }

private static async Task FooAsync() {     await Task.Delay(1000);     Console.WriteLine(“Done with first delay”);     await Task.Delay(1000); }

You could also use a custom SynchronizationContext or TaskScheduler to achieve similar capabilities. For more information, see:

Can I use “await” with other asynchronous patterns, like the Asynchronous Programming Model (APM) pattern and the Event-based Async Pattern (EAP)?

Sure. You can either implement a custom awaitable for your asynchronous operation, or you can convert the existing asynchronous operation to something that’s already awaitable, like Task or Task<TResult>. Here are some examples:

Does the code generated by async/await result in efficient asynchronous execution?

For the most part, yes, as a lot of work has been done to optimize the code generated by the compiler and the .NET Framework methods on which the generated code relies. For more information, including on best practices for minimizing the overhead of using tasks and async/await, see:

Async/Await FAQ的更多相关文章

  1. Async/Await - Best Practices in Asynchronous Programming z

    These days there’s a wealth of information about the new async and await support in the Microsoft .N ...

  2. [译]async/await中阻塞死锁

    这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock o ...

  3. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  4. 【转】C# Async/Await 异步编程中的最佳做法

    Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...

  5. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  6. Async/Await 最佳实践

    其实好久以前就看过这个文章,以及类似的很多篇文章.最近在和一个新同事的交流中发现原来对async的死锁理解不是很透彻,正好最近时间比较充裕就再当一回搬运工. 本文假定你对.NET Framework ...

  7. async/await 异步编程

    前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在 ...

  8. [翻译] Python 3.5中async/await的工作机制

    Python 3.5中async/await的工作机制 多处翻译出于自己理解,如有疑惑请参考原文 原文链接 身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇.尽管我很清楚自己不可能对 ...

  9. C# 异步编程(async&await)

    同步:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去 异步:异步是指进程不需要一直等下去,而是继续执行下面的操作 ...

随机推荐

  1. 绘制相切弧arcTo

    绘制相切弧 语法: CanvasRenderingContext2D.arcTo( x1, y1, x2, y2, radius ) 描述: 该方法用于绘制圆弧 绘制的规则是当前位置与第一个参考点连线 ...

  2. tunning-Instruments and Flame Graphs

    On mac os, programs may need Instruments to tuning, and when you face too many probe messages, you'l ...

  3. 附加数据库失败,sql2008,断电数据库日志受损

    附加数据库失败,提示:无法在数据库 'DBNAME' (数据库 ID 为 7)的页 (1:210288) 上重做事务 ID (0:0) 的日志记录或者在重做数据库 'DBNAME' 的日志中记录的操作 ...

  4. XAF 14.1 DC 实现自定审计日志信息

    由于一个系统的需要,需要在日志中加入自定义的信息,并且需要根据需要过滤显示其中的部分操作记录入修改,删除等,其他的不显示,具体的实现方法如下: 一.需要继承 AuditDataItemPersiste ...

  5. Hibernate 知识点梳理

    1.对持久化对象的要求 1)提供一个无参构造器 2)提供一个标识属性,如id,通常映射为数据库表的主键字段. 3)为持久化类的字段提供get.set方法. 注:但不一定所有字段都这么做,对于不提供ge ...

  6. notepad++ 离线插件下载

    http://www.cnblogs.com/findumars/p/5180562.html

  7. 利用Jurassic在.net下运行js函数

    static void Main(string[] args) { var eng = new Jurassic.ScriptEngine(); eng.Evaluate("function ...

  8. [z]查表空间使用情况

    SELECT UPPER(F.TABLESPACE_NAME) "表空间名",D.TOT_GROOTTE_MB "表空间大小(M)",D.TOT_GROOTTE ...

  9. Netty 的 inbound 与 outbound, 以及 InboundHandler 的 channelInactive 与 OutboundHandler 的 close

    先看一个例子. 有一个简单 Server public class SimpleServer { public static void main(String[] args) throws Excep ...

  10. js--webSocket入门

    Websocket 1.websocket是什么? WebSocket是为解决客户端与服务端实时通信而产生的技术.其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接, 此 ...