我不太明白 Task.Wait
和 await
之间的区别。
我在 ASP.NET WebAPI 服务中有类似于以下函数的东西:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
其中 Get
将死锁。
什么可能导致这种情况?当我使用阻塞等待而不是 await Task.Delay
时,为什么这不会导致问题?
Task.Delay(1).Wait()
一起使用,这已经足够好了。
Task.Delay(1).Wait()
基本上与 Thread.Sleep(1000)
完全相同。在实际的生产代码中,它很少是合适的。
WaitAll
导致了死锁。有关更多详细信息,请参阅我的答案中指向我的博客的链接。您应该改用 await Task.WhenAll
。
ConfigureAwait(false)
对 Bar
或 Ros
的 single 调用不会死锁,而是因为您有一个创建多个然后等待所有这些的枚举,第一个栏将死锁第二个。如果您 await Task.WhenAll
而不是等待所有任务,这样您就不会阻塞 ASP 上下文,您将看到该方法正常返回。
.ConfigureAwait(false)
一直向上 直到您阻塞,这样就没有任何东西 ever 试图回到主要上下文;那行得通。另一种选择是启动内部同步上下文。 Link。如果您将 Task.WhenAll
放在 AsyncPump.Run
中,它将有效地阻止整个事情,而您无需在任何地方使用 ConfigureAwait
,但这可能是一个过于复杂的解决方案。
Wait
和 await
- 虽然在概念上相似 - 实际上完全不同。
Wait
将同步阻塞,直到任务完成。因此,当前线程实际上被阻塞,等待任务完成。作为一般规则,您应该一直使用“async
”;也就是说,不要阻塞 async
代码。在我的博客上,我详细介绍了 how blocking in asynchronous code causes deadlock。
await
将异步等待任务完成。这意味着当前 方法 处于“暂停”状态(它的状态被捕获)并且该方法向其调用者返回一个不完整的任务。稍后,当 await
表达式完成时,该方法的其余部分被安排为延续。
您还提到了“合作块”,我假设您的意思是您正在处理的任务可能会在等待线程上执行。Wait
在某些情况下可能会发生这种情况,但这是一种优化。在很多情况下它不可能发生,例如任务是针对另一个调度程序,或者它已经启动或者它是非代码任务(例如在您的代码示例中:{1 } 无法内联执行 Delay
任务,因为它没有代码)。
您可能会发现我的 async
/ await
intro 很有帮助。
根据我从不同来源阅读的内容:
await
表达式不会阻塞它正在执行的线程。相反,它会导致编译器注册 async
方法的其余部分作为等待任务的延续。然后控制权返回给 async
方法的调用者。当任务完成时,它调用它的继续,并且 async
方法的执行从它停止的地方继续。
要等待单个 task
完成,您可以调用其 Task.Wait
方法。对 Wait
方法的调用会阻塞调用线程,直到单个类实例完成执行。无参数 Wait()
方法用于无条件等待任务完成。该任务通过调用 Thread.Sleep
方法休眠两秒钟来模拟工作。
This article 也是一本好书。
其他答案中没有给出一些重要的事实:
“异步等待”在 CIL 级别更复杂,因此会消耗内存和 CPU 时间。
如果等待时间不可接受,任何任务都可以取消。
在“异步等待”的情况下,我们没有处理此类任务的处理程序来取消它或监视它。
使用 Task 比“异步等待”更灵活。
任何同步功能都可以由异步包装。
public async Task<ActionResult> DoAsync(long id)
{
return await Task.Run(() => { return DoSync(id); } );
}
“异步等待”会产生很多问题。我们现在不是在没有运行时和上下文调试的情况下会到达 await 语句。如果未达到第一个等待,则所有内容都将被阻止。有时甚至等待似乎到达仍然一切都被阻止:
https://github.com/dotnet/runtime/issues/36063
我不明白为什么我必须忍受同步和异步方法或使用黑客的代码重复。
结论:手动创建任务并控制它们要好得多。 Task 的处理程序给予更多的控制。我们可以监控任务并管理它们:
https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem
对不起我的英语不好。
不定期副业成功案例分享
Wait
工作正常await
死锁。Wait
阻塞线程,不能用于其他事情。await
代码一起使用。要么,要么死锁与任何一个都无关,你错误地诊断了问题。SynchronizationContext
解决了此问题,因此 ASP.NET Core 请求中的阻塞不再出现死锁。