代码如下:
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" );
}
}
下面是输出:
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
正如您所看到的,finally块的执行时间比您预期的要晚得多。
有什么解决方法吗?
提前感谢!
发布于 2011-02-18 18:22:16
这是一个很好的捕获-我同意这里的CTP中实际上存在一个bug。我深入研究了一下,这是怎么回事:
这是异步编译器转换的CTP实现,以及来自.NET 4.0+的任务并行库的现有行为的组合。以下是起作用的因素:
来自源代码的最终主体被翻译成真正的-
Func(int n)
方法的总体Task
是一个真正的第三方逻辑任务。当你在await
中输入Consumer()
时,Consumer()
方法的其余部分实际上是作为完成从Func(int n)
.Task
的延续安装的。CTP编译器转换异步方法的方式是在实际返回之前将return
映射到SetResult(...)
调用。TPL归结为对TaskCompletionSource<>.TrySetResult
.TaskCompletionSource<>.TrySetResult
的调用,表示SetResult(...)
任务完成。立即使它的继续发生在“某个时候”。这个“某一时刻”可能意味着在另一个线程上,或者在某些情况下,第三方公共关系很聪明,会说“嗯,我也可以现在就在这个线程上调用它”。Func(int n)
的总体Task
在最终运行之前在技术上变得“完成”。这意味着等待异步方法的代码可以在并行线程中运行,甚至可以在finally块之前运行。考虑到总体Task
应该表示方法的异步状态,基本上它不应该被标记为完成,直到至少所有用户提供的代码都按照语言设计执行了。我将向Anders、语言设计团队和编译器开发人员提出这一点,以了解这一点。
表现形式/严重程度的范围:
在WPF或WinForms的情况下,您通常不会受到这种情况的影响,因为您正在进行某种托管消息循环。原因是Task
实现上的await
遵循SynchronizationContext
。这会导致异步延续在预先存在的消息循环上排队,以便在同一线程上运行。您可以通过以下方式更改代码以运行Consumer()
来验证这一点:
DispatcherFrame frame = new DispatcherFrame(exitWhenRequested: true);
Action asyncAction = async () => {
await Consumer();
frame.Continue = false;
};
Dispatcher.CurrentDispatcher.BeginInvoke(asyncAction);
Dispatcher.PushFrame(frame);
在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
解决方法:
遗憾的是,变通方法意味着更改代码,使其不在try/finally
块中使用return
语句。我知道这真的意味着你在代码流中失去了很多优雅。你可以使用async helper方法或者helper lambda来解决这个问题。就我个人而言,我更喜欢helper-lambdas,因为它自动关闭包含方法的本地变量/参数,并使您的相关代码更接近。
助手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;
}
帮助方法方法:
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;
}
作为一个厚颜无耻的插头:我们正在微软的语言空间招聘,并一直在寻找优秀的人才。博客条目here,包含空缺职位的完整列表:)
https://stackoverflow.com/questions/5032784
复制相似问题