Consider a type that will print out a message when it’s finalized, and that has a Dispose method which will suppress finalization:

class DisplayOnFinalize : IDisposable {     public void Dispose() { GC.SuppressFinalize(this); }     ~DisplayOnFinalize() { Console.WriteLine(“Finalized”); } }

Now consider a simple usage of this class:

void Foo() {     var tcs = new TaskCompletionSource<bool>();     using(new DisplayOnFinalize())     {         tcs.Task.Wait();     } }

This method instantiates an instance of the finalizable class, and then blocks waiting for a task to be completed prior to disposing of the DisplayOnFinalize instance.  The task on which this code waits will never complete, and thus the calling thread will remain blocked at this location.  That thread’s stack will maintain a reference to the DisplayOnFinalize class, since if the wait were to complete the thread would need to invoke that instance’s Dispose method.  And as such, the message will never be printed out.

Now, consider a small variation:

async void Foo() {     var tcs = new TaskCompletionSource<bool>();     using(new DisplayOnFinalize())     {         await tcs.Task;     } }

The only differences here are that I’ve added the async keyword to the signature of my method, and I’m now asynchronously waiting for the task to complete (via the await keyword) rather than synchronously waiting for it to complete (via the Wait method).  However, this has a significant effect on behavior.  Of course, there’s the important difference that you’d expect, that we’re asynchronously waiting for the task and thus we’re not blocking the calling thread while waiting (forever) for the task to complete.  However, there’s a more subtle but nevertheless significant difference here… if you were to call this method repeatedly, you’d start to see “Finalized” getting printed out as the DisplayOnFinalize instances got garbage collected and finalized.

The async/await keywords tell the C#/Visual Basic compiler to rewrite your async method into a state machine, where the code that comes after the await is logically part of a continuation hooked up to the task (or, in general, the awaitable) being awaited.  The following isn’t exactly how the transformation happens, but you can think of the previous example as logically translating into something like the following (for the sake of this post, I’m leaving out lots of otherwise important details):

void Foo() {     var tcs = new TaskCompletionSource<bool>();     var d = new DisplayOnFinalize();     tcs.Task.ContinueWith(delegate     {         d.Dispose();     }); }

The code that comes after the await is in effect hooked up as a continuation, and in this case that code is the code to dispose of the DisplayOnFinalize instance.  Now, when the call to Foo returns, there’s no more reference via the thread’s stack to ‘d’.  The only reference to ‘d’ is in the closure/delegate hooked up to the task as a continuation.  If that task were rooted, that would be enough to keep the DisplayOnFinalize instance alive, but the task isn’t rooted.  The task is referred to by the TaskCompletionSource<bool> instance ‘tcs’ on the thread’s stack, but when the call to Foo goes away, so too does that reference to ‘tcs’.  And thus, all of these instances become available for garbage collection.

All of this serves to highlight an important fact: when you await something, it’s that something which needs to keep the async method’s execution alive via a reference to the continuation object that’s provided by ‘await’ to the awaited awaitable’s awaiter (try saying that ten times fast).  When you await Task.Run(…), the task you’re awaiting is rooted in the ThreadPool’s queues (or if the task is already executing, it’s rooted by the stack processing the task).  When you await Task.Delay(…), the task you’re awaiting is rooted by the underlying timer’s internal data structures.  When you await a task for an async I/O operation, the task is typically rooted by some data structure held by the I/O completion port that will be signaled when the async operation completes.  And so on.

This all makes logical sense: in order to complete the task when the async operation completes, the thing that will be completing it must have a reference to it.  But it’s still something good to keep in mind… if you ever find that your async methods aren’t running to completion, consider whether awaitables you’re awaiting might be getting garbage collected before they complete.  In my previous post, I referred to a real bug that was discovered to be caused by a task never completing.  The way we happened across that bug was because a finalizable class similar in nature to the one shown previously was actually in use, and its finalizer was firing.  This led us to realize that it was invoked because the awaited task was getting garbage collected before it was completed, which was because of a a queue of work referencing completion sources, and that queue that was being cleared without canceling those associated tasks.

Keeping Async Methods Alive的更多相关文章

  1. 使用Async方法 Using Async Methods 精通ASP-NET-MVC-5-弗瑞曼 Listing 4-32.

  2. Async/Await FAQ

    From time to time, I receive questions from developers which highlight either a need for more inform ...

  3. 编程概念--使用async和await的异步编程

    Asynchronous Programming with Async and Await You can avoid performance bottlenecks and enhance the ...

  4. Async Performance: Understanding the Costs of Async and Await

    Stephen Toub Download the Code Sample Asynchronous programming has long been the realm of only the m ...

  5. await and async

    Most people have already heard about the new “async” and “await” functionality coming in Visual Stud ...

  6. C# Async, Await and using statements

    Async, Await 是基于 .NEt 4.5架构的, 用于处理异步,防止死锁的方法的开始和结束, 提高程序的响应能力.比如: Application area           Support ...

  7. (译)关于async与await的FAQ

    传送门:异步编程系列目录…… 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的API及性能提升,另外关键字”async” ...

  8. Async and Await

    http://blog.stephencleary.com/2012/02/async-and-await.html Most people have already heard about the ...

  9. Don't Block on Async Code【转】

    http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html This is a problem that is brough ...

随机推荐

  1. 关于c#静态构造函数

    http://baike.baidu.com/view/2634573.htm?fr=aladdin 在百科上看到C#的新特性静态构造函数,其中提到静态构造函数“不能继承” 今天做了个试验,发现实际上 ...

  2. mysql中的SUBSTRING_INDEX

    SUBSTRING_INDEX(str,delim,count) Returns the substring from string str before count occurrences of t ...

  3. Hibernate持久化类属性映射

    Hibernate充当应用程序和数据库之间的中间件,实现二者之间的交互操作,他对JDBC进行了封装,以完全面向对象的方式来操作数据. 适用于有多个数据源的情况下,不必去考虑不同数据源的操作差异. Hi ...

  4. Xcode中给控件添加颜色时自动显示出颜色

    在iOS开发中,给一些控件设置颜色的时候,设置完不能立马看到颜色.必须要运行程序之后才能看到设置的颜色,如果颜色有偏差再回代码改参数,然后再运行看颜色很是麻烦.令人高兴得是Xcode有很多功能强大插件 ...

  5. 自定义View(二)增加View的属性

    增加View的属性有两种方法    1.在View类中添加    2.在xml资源文件中添加 一.在View类中添加    例:实现一个带文字的图片 public class MyView exten ...

  6. 读取xml文件报错:Invalid byte 2 of 2-byte UTF-8 sequence。

    程序读取xml文件后,系统报“Invalid byte 2 of 2-byte UTF-8 sequence”错误,如何解决呢? 1.程序解析xml的时候,出现Invalid byte 2 of 2- ...

  7. JS验证字符长度

    function getStrLength(str) { var cArr = str.match(/[^\x00-\xff]/ig); return str.length + (cArr == nu ...

  8. 如何在在WinFrom的DataGridView中做到数据持续动态加载而不卡死

    1.在这个过程我用过好几种办法 (1)使用委托的办法,这个方法可以做到持续加载,但是效果不理想会卡死 (2)开启线程的方法,会造成卡死 (3)使用另一个窗体的线程做持续加载(子窗体),让子窗体作为一个 ...

  9. 迅雷VIP帐号获取小工具

    自己写的迅雷vip帐号获取工具,主要是熟悉一下正则表达式 下载地址: 迅雷VIP获取工具 另附vip防踢补丁,不能使用最新迅雷,我使用的是迅雷尊享版2.0.12.258,使用了一段时间,至少没被踢出来 ...

  10. windows下搭建scrapywindows 7 (64) + python 3.5 (64)

    说明 之前在 window 10 (64) + python 3.5 (64) 环境下就已经成功安装了 scrapy,当然也费了不少周折. 由于近日将系统换回 windows 7 (64),再安装 s ...