Keeping Async Methods Alive
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的更多相关文章
- 使用Async方法 Using Async Methods 精通ASP-NET-MVC-5-弗瑞曼 Listing 4-32.
- Async/Await FAQ
From time to time, I receive questions from developers which highlight either a need for more inform ...
- 编程概念--使用async和await的异步编程
Asynchronous Programming with Async and Await You can avoid performance bottlenecks and enhance the ...
- 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 ...
- await and async
Most people have already heard about the new “async” and “await” functionality coming in Visual Stud ...
- C# Async, Await and using statements
Async, Await 是基于 .NEt 4.5架构的, 用于处理异步,防止死锁的方法的开始和结束, 提高程序的响应能力.比如: Application area Support ...
- (译)关于async与await的FAQ
传送门:异步编程系列目录…… 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的API及性能提升,另外关键字”async” ...
- Async and Await
http://blog.stephencleary.com/2012/02/async-and-await.html Most people have already heard about the ...
- 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 ...
随机推荐
- jq 部分用法
这几天一直在写前台,因为jq是在客服端处理数据的,所以公司,一般都用这种方法,下面是我这几天用到的一些东西 1.修改table表格的第一轮显示值 function changeTableRowValu ...
- Linux下Java安装与配置
一.卸载系统自带的JDK 如果Linux已经自带OpenJdk,我们需要将它卸载掉,否则可以直接[安装JDK] 查看Linux自带的JDK是否已安装,输入如下命令查看JAVA版本信息. java -v ...
- ssh链接数设置问题
今天碰到一个问题,脚本执行scp文件拷贝,因为拷贝的服务器很多,所以拷贝脚本的实现是在把拷贝动作转后台执行,结果发现一堆文件拷贝失败.比较有迷惑性的是,拷贝失败的通常是同一个文件夹拷贝到所有服务器时失 ...
- BingMap
Application name Key details BngMapTest Key:25nTPiuDe0kxITMR1ymE~j5IlskEImiwGsGmAnsCftQ~Ap0HigfJujLq ...
- iOS开发-automaticallyAdjustsScrollViewInsets属性
iOS开发-automaticallyAdjustsScrollViewInsets属性 Available in iOS 7.0 and later. 简单点说就是automaticallyAdju ...
- 完美解决全面屏蔽Google教程(终结者)
最近谷歌的IP被大范围的禁用了.身处一个连谷歌都用不了的过度的程序员,深感命运多舛.幸好,魔高一尺,道高一丈.下面是几种可以使用谷歌的方法. 方法一 1)在chrome浏览器中输入:chrome:// ...
- Python中的threading
Python中的threading RLock--重入锁 RLock在Python中的实现是对Lock的封装,具体在类中维护了一个重入次数的变量.一旦一个线程获得一个RLock,该线程再次要求获得该锁 ...
- php和syslog
syslog是Linux系统默认的日志守护进程.使用syslog可以方便把指定的事件写入特定文件中,可以让任何事件都登录到一台或多台服务器上. 1.简单例子,先说一下syslog怎么使用,以php为例 ...
- 安卓使用adb命令安装软件
准备工作: 确信 \Android-sdk-windows\tools\下有 adb.exe AdbWinApi.dll AdbWinUsbApi.dll 三个文件,如果没有从\and ...
- JS、C#及SQL中的DateTime
一:SQL中的DataTime 1. between and 相当于>= and <= 2. 常用的将DataTime查询成字符串的方法 Select CONVER ...