无法等待异步lambda函数

75

请考虑这个问题,

Task task = new Task (async () =>{
    await TaskEx.Delay(1000);
});
task.Start();
task.Wait(); 

调用task.Wait()方法不会等待任务完成,而是立即执行下一行代码,但是如果我将异步lambda表达式包装到方法调用中,代码将按预期工作。

private static async Task AwaitableMethod()
{
    await TaskEx.Delay(1000);    
}

然后(根据svick的评论更新)

await AwaitableMethod(); 

AwaitableMethod 中,您实际上是返回并调用了来自 .Delay() 方法的任务(我假设它返回一个 Task)。在异步 lambda 中,您正在调用 Task task 上的 Wait。但是,我仍然没有解释。 - Mario S
2
在将 awaitWait() 混合使用时,您应该非常小心。在许多情况下,这可能会导致死锁。 - svick
@svick在stackoverflow上找到了一个很好的例子,关于如何混合使用awaitWait() - kennyzx
2个回答

98
在您的 lambda 示例中,当您调用 task.Wait() 时,您正在等待您构造的新 Task,而不是它返回的 delay Task。要获得所需的延迟,您还需要等待生成的 Task:
Task<Task> task = new Task<Task>(async () => {
    await Task.Delay(1000);
});
task.Start();
task.Wait(); 
task.Result.Wait();

您可以避免构建一个新的Task,只需处理一个而不是两个:

Func<Task> task = async () => {
    await TaskEx.Delay(1000);
};
task().Wait();

1
如果第一个await在大量处理之后,您仍然可能需要双重任务。您可以使用task.Unwrap().Wait()(或对于非void方法,使用Unwrap<T>())而不是task.Result.Wait()。新的Task.Run方法会自动展开,因此您只需等待预期的任务即可。 - Nelson Rothermel
8
作为一个初学者,我认为使用async关键字的时候,他们本可以做得更好一些;这让人感到非常困惑。 - drowa
是否可以启动容器任务而不启动嵌套任务? - drowa
@drowa async 期望函数返回 "热" 任务,这些任务应该是已经启动的函数返回的。如果您等待一个未启动、也不会启动的任务,则程序将永远无法从等待中返回。唯一的 "不启动嵌套任务" 的方法是在容器函数中,在不调用创建嵌套任务的内部函数的情况下完成。 - Scott Chamberlain
13
潜在陷阱的链接已经失效,现在正确的链接是https://devblogs.microsoft.com/pfxteam/potential-pitfalls-to-avoid-when-passing-around-async-lambdas/。请注意更新链接。 - VoteCoffee

8
您需要使用 TaskEx.RunEx
它原生支持在 TaskPool 上运行异步方法,内部等待内部任务。否则,您将遇到您面临的问题,仅等待外部任务,这显然会立即完成,留下仍需等待的任务,或在您的情况下(甚至更糟)无法等待的void lambda。
或者,您可以两次等待任务,前提是正确构造外部任务(目前您没有这样做)。
当前代码(已修复):
Task task = new Task<Task>(async () =>{
    await TaskEx.Delay(1000);
});

task.Start();
var innerTask = await task;
await innerTask;

使用 TaskEx.RunEx:
Task task = TaskEx.RunEx(async () =>{ // Framework awaits your lambda internally.
    await TaskEx.Delay(1000);
});

await task;

很好的解释,但是代码TaskEx.Run不起作用,仍然存在同样的问题。 - kennyzx
2
啊,抱歉!我正在使用.NET 4.5... 我的意思是要写TaskEx.RunEx。将其签名与TaskEx.Run进行比较-您会看到它为运行异步方法而特别设计。 - Lawrence Wagerfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接