[英]Async CTP and “finally”
Here's the code: 这是代码:
static class AsyncFinally
{
static async Task<int> Func( int n )
{
try
{
Console.WriteLine( " Func: Begin #{0}", n );
await TaskEx.Delay( 100 );
Console.WriteLine( " Func: End #{0}", n );
return 0;
}
finally
{
Console.WriteLine( " Func: Finally #{0}", n );
}
}
static async Task Consumer()
{
for ( int i = 1; i <= 2; i++ )
{
Console.WriteLine( "Consumer: before await #{0}", i );
int u = await Func( i );
Console.WriteLine( "Consumer: after await #{0}", i );
}
Console.WriteLine( "Consumer: after the loop" );
}
public static void AsyncTest()
{
Task t = TaskEx.RunEx( Consumer );
t.Wait();
Console.WriteLine( "After the wait" );
}
}
Here's the output: 这是输出:
Consumer: before await #1
Func: Begin #1
Func: End #1
Consumer: after await #1
Consumer: before await #2
Func: Begin #2
Func: Finally #1
Func: End #2
Consumer: after await #2
Consumer: after the loop
Func: Finally #2
After the wait
As you can see, the finally block is executed much later then you'd expect. 正如您所看到的,finally块会在您预期的时间之后执行。
Any workarounds? 任何解决方法?
Thanks in advance! 提前致谢!
This is an excellent catch - and I agree that there is actually a bug in the CTP here. 这是一个很好的捕获 - 我同意这里的CTP实际上存在一个错误。 I dug into it and here's what's going on:
我挖了它,这是发生了什么:
This is a combination of the CTP implementation of the async compiler transformations, as well as the existing behavior of the TPL (Task Parallel Library) from .NET 4.0+. 这是异步编译器转换的CTP实现以及.NET 4.0+中TPL(任务并行库)的现有行为的组合。 Here are the factors at play:
以下是发挥作用的因素:
Task
for the Func(int n)
method is a real TPL task. Func(int n)
方法的首要Task
是一个真正的TPL任务。 When you await
in Consumer()
, then the rest of the Consumer()
method is actually installed as a continuation off of the completion of the Task
returned from Func(int n)
. Consumer()
await
时,其余的Consumer()
方法实际上是作为从Func(int n)
返回的Task
完成的继续安装的。 return
being mapped to a SetResult(...)
call prior to a real return. return
在实际返回之前映射到SetResult(...)
调用。 SetResult(...)
boils down to a call to TaskCompletionSource<>.TrySetResult
. SetResult(...)
归结为对TaskCompletionSource<>.TrySetResult
的调用TaskCompletionSource<>.TrySetResult
。 TaskCompletionSource<>.TrySetResult
signals the completion of the TPL task. TaskCompletionSource<>.TrySetResult
表示TPL任务已完成。 Instantly enabling its continuations to occur "sometime". Task
for Func(int n)
becomes technically "Completed" right before the finally gets run. Func(int n)
的首要Task
在技术上变为“已完成”。 This means that code that was awaiting on an async method may run in parallel threads, or even before the finally block. Considering the overarching Task
is supposed to represent the asynchronous state of the method, fundamentally it shouldn't get flagged as completed until at least all the user-provided code has been executed as per the language design. 考虑到总体
Task
应该代表方法的异步状态,从根本上说,它至少应该被标记为已完成,直到至少所有用户提供的代码都按照语言设计执行。 I'll bring this up with Anders, language design team, and compiler devs to get this looked at. 我将与Anders,语言设计团队和编译器开发人员一起讨论这个问题。
Scope of Manifestation / Severity: 表现范围/严重程度:
You typically won't be bit by this as bad in a WPF or WinForms case where you have some sort of managed message loop going on. 在WPF或WinForms中你通常会遇到某种托管消息循环,你通常不会觉得这很糟糕。 The reason why is that the
await
on Task
implementations defer to the SynchronizationContext
. 原因是
Task
实现上的await
遵循SynchronizationContext
。 This causes the async continuations to be queued up on the pre-existing message loop to be run on the same thread. 这会导致异步连续在预先存在的消息循环上排队,以便在同一个线程上运行。 You can verify this by changing your code to run
Consumer()
in the following way: 您可以通过以下方式更改代码以运行
Consumer()
来验证这一点:
DispatcherFrame frame = new DispatcherFrame(exitWhenRequested: true);
Action asyncAction = async () => {
await Consumer();
frame.Continue = false;
};
Dispatcher.CurrentDispatcher.BeginInvoke(asyncAction);
Dispatcher.PushFrame(frame);
Once run inside the context of the WPF message loop, the output appears as you would expect it: 一旦在WPF消息循环的上下文中运行,输出就会如您所期望的那样出现:
Consumer: before await #1
Func: Begin #1
Func: End #1
Func: Finally #1
Consumer: after await #1
Consumer: before await #2
Func: Begin #2
Func: End #2
Func: Finally #2
Consumer: after await #2
Consumer: after the loop
After the wait
Workaround: 解决方法:
Alas, the workaround means changing your code to not use return
statements inside a try/finally
block. 唉,解决方法意味着将代码更改为不在
try/finally
块中使用return
语句。 I know this really means you lose a lot of elegance in your code flow. 我知道这真的意味着你在代码流中失去了很多优雅。 You can use async helper methods or helper lambdas to work around this.
您可以使用异步辅助方法或帮助程序lambdas来解决此问题。 Personally, I prefer the helper-lambdas because it automatically closes over locals/parameters from the containing method, as well as keeps your relevant code closer.
就个人而言,我更喜欢helper-lambdas,因为它会自动关闭包含方法的locals /参数,并使您的相关代码更接近。
Helper Lambda approach: Helper Lambda方法:
static async Task<int> Func( int n )
{
int result;
try
{
Func<Task<int>> helperLambda = async() => {
Console.WriteLine( " Func: Begin #{0}", n );
await TaskEx.Delay( 100 );
Console.WriteLine( " Func: End #{0}", n );
return 0;
};
result = await helperLambda();
}
finally
{
Console.WriteLine( " Func: Finally #{0}", n );
}
// since Func(...)'s return statement is outside the try/finally,
// the finally body is certain to execute first, even in face of this bug.
return result;
}
Helper Method approach: 辅助方法方法:
static async Task<int> Func(int n)
{
int result;
try
{
result = await HelperMethod(n);
}
finally
{
Console.WriteLine(" Func: Finally #{0}", n);
}
// since Func(...)'s return statement is outside the try/finally,
// the finally body is certain to execute first, even in face of this bug.
return result;
}
static async Task<int> HelperMethod(int n)
{
Console.WriteLine(" Func: Begin #{0}", n);
await TaskEx.Delay(100);
Console.WriteLine(" Func: End #{0}", n);
return 0;
}
As a shameless plug: We're hiring in the languages space at Microsoft, and always looking for great talent. 作为一个无耻的插件:我们正在微软的语言空间招聘,并一直在寻找优秀的人才。 Blog entry here with the full list of open positions :)
博客条目在这里有完整的未结头寸列表:)
Please consider Theo Yaung's answer . 请考虑Theo Yaung的答案 。
I'm not familiar with async/await, but after reading this: Visual Studio Async CTP Overview 我不熟悉async / await,但在阅读之后: Visual Studio异步CTP概述
and reading your code, I see the await
in the Func(int n)
function, meaning that from the code after the await
keyword till the end of the function will be executed later as a delegate. 并且读取你的代码,我在
Func(int n)
函数中看到await
,这意味着从await
关键字之后的代码到函数的结尾稍后将作为委托执行。
So my guess (and this is an uneducated guess) is that the Func:Begin
and Func:End
will possibly execute in different "contexts" (threads?), ie, asynchronously. 所以我的猜测 (这是一个没有受过教育的猜测)是
Func:Begin
和Func:End
可能会在不同的“上下文”(线程?)中执行,即异步执行。
Thus, the int u = await Func( i );
因此,
int u = await Func( i );
line in Consumer
will continue its execution the moment the code await
in Func
will be reached. Consumer
中的行将在达到Func
的代码await
时继续执行。 So it is quite possible to have: 所以很有可能:
Consumer: before await #1
Func: Begin #1
Consumer: after await #1
Consumer: before await #2
Func: Begin #2
Consumer: after await #2
Consumer: after the loop
Func: End #1 // Can appear at any moment AFTER "after await #1"
// but before "After the wait"
Func: Finally #1 // will be AFTER "End #1" but before "After the wait"
Func: End #2 // Can appear at any moment AFTER "after await #2"
// but before "After the wait"
Func: Finally #2 // will be AFTER "End #2" but before "After the wait"
After the wait // will appear AFTER the end of all the Tasks
The Func: End
and Func: Finally
can appear in whatever position in the logs, the only constraint being that a Func: End #X
will appear before its associated Func: Finally #X
, and that both should appear before the After the wait
. Func: End
和Func: Finally
可以出现在日志中的任何位置,唯一的约束是Func: End #X
将出现在其关联的Func: Finally #X
,并且两者都应该出现在After the wait
。
As explained (somewhat abruptly) by Henk Holterman is that the fact you put an await
in the Func
body means that everything after will be executed sometimes after. 正如Henk Holterman所解释的那样(有点突然),你在
Func
体中await
的事实意味着之后的所有事情有时会被执行。
There is no workaround as, by design
you put a await
between the Begin
and the End
of Func
. 没有解决方法,因为
by design
你在Func
的Begin
和End
之间进行了await
。
Just my uneducated 2 eurocents. 只是我未受过教育的2欧元。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.