异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]

Posted

技术标签:

【中文标题】异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]【英文标题】:async/await deadlock Task.WaitAll vs Task.WhenAll [duplicate] 【发布时间】:2018-08-26 02:05:16 【问题描述】:

我有下面的代码,它只是挂在 Task.WaitAll 行上。我猜这是由于死锁,其中主线程正在等待其同步上下文以继续执行,但其中一个延续也需要此上下文才能完成此处概述的执行:

http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

 protected override async Task<Person> GetOrders()
 
     var person = new Person();
     var task1 = GetPersonOrders(person);
     var task2 = GetPersonAddresses(person);
     var tasks = new List<Task>()task1, task2
     Task.WaitAll(tasks.ToArray());
 

public async Task GetPersonOrders(Person person)

   ...
   person.PersonOrders = await GetPersonOrdersFromRepository();


public async Task<List<Order>> GetPersonOrdersFromRepository()

   ...
   return await CallSomeWebService;


public async Task GetPersonAddresses(Person person)

   ...
   person.Addresses = await GetPersonAddressesFromRepository(); 


public async Task<List<Address>> GetPersonAddressesFromRepository()

   ...
   return await CallSomeWebService;

问题

1) 为什么将GetOrders()的final改为

await Task.WhenAll(tasks);

解决所有问题?为什么通过等待Task.WhenAll over Task.WaitAll解决了死锁?

2) 这些任务是否仍与 Task.WhenAll 修改并行运行?

【问题讨论】:

await 进行协作多任务处理,而不是并行处理。 await WhenAll配合调用者,WaitAll不仅不配合调用者,也不配合子任务。 其实...WaitAll() 需要手动启动任务吗?也许这就是这里的区别? @BenVoigt - 我删除了我的评论,因为是的,我认为这是不正确的。我有点不确定“多任务处理不是并行处理”是什么意思。一般来说,是的。但是,如果一个人按顺序启动两个真正的异步任务,我发现给定可用线程,它们会并行处理(每个都有自己的线程)。这个例子我错了吗? @zzxyz:这取决于您运行的环境(特别是TaskScheduler.Current 实现。)即使GetPersonAddressesFromRepositoryCallSomeWebService 使用了其他线程(他们可能没有,因为 I/O 本质上是异步的),当你 await 他们一些 TaskSchedulers 将尝试在同一个线程上执行其余的方法。这发生在 WPF、Windows 窗体和现代版本的 ASP.NET 等环境中。 【参考方案1】:

为什么将 GetOrders() 的 final 改为 await Task.WhenAll(tasks); 修复一切?为什么通过等待Task.WhenAll over Task.WaitAll解决了死锁?

因为只要你点击awaitGetOrders()方法就会返回一个Task,表示它已经同步完成了所有能做的事情,其他任务可以接管这个线程。

当您安排的任务从网络服务器、存储库等获得响应时,任务调度程序能够分配一个线程以完成任务的继续。

如果您在可以在并行同步上下文中完成这些任务的环境中运行它,那么即使使用 Task.WaitAll(),您也不会陷入死锁。

同样,如果您在调用堆栈中始终使用ConfigureAwait(false)每个 await,那么您也不会遇到这个问题,因为这会通知任务调度程序,您不关心生成的任务是否在它开始的同一个同步上下文中继续。

这些任务是否仍与 Task.WhenAll 修改并行运行?

没有。它们同时运行,但不是并行运行。

请记住,此问题源于任务无法继续执行,直到它们启动的线程被释放。它们一次只允许在一个线程上运行这一事实意味着它们不会并行运行。

更多详情请见What is the difference between asynchronous programming and multithreading?。

【讨论】:

“请记住,此问题源于任务无法在它们开始的同一线程上继续执行。”您的意思是相同的同步上下文对吗? .....我将如何并行运行这些异步任务...使用 Parallel.ForEach? 我有点困惑。是什么导致这些任务绑定到单个线程?我相信我理解这种区别,但我不明白为什么它适用于这个例子。我通常会使用UIElement.Dispatcher.Invoke() 从工作任务更新 UI,我只是觉得我从来没有遇到过除了 C# 中的语义之外的任何东西。但显然可以。 好的,你基本上回答了上面这个问题。谢谢。 它充分说明了这些类的设计有多好,我已经如此广泛地使用它们,而我对它们如何工作的知识水平显然有点低。 @StriplingWarrior “因为这会通知任务调度程序你不关心结果任务是否在它开始的同一个线程上继续。” ...当我在执行任何其他代码之前检查 GetOrders() 中的 threadId,然后在任何延续中进行相同的检查...虽然同步上下文相同(AspNetSynchContext),但 threadId 不同。它不应该是同一个 threadId 吗?...这是没有修改代码以在调用链中反映 ConfigureAwait(false)。

以上是关于异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Task.WaitAll代替WaitHandle.WaitAll

Task.WaitAll在一个任务抛出时挂起,另一个任务与第一个任务同步

Task.WaitAll和Task.WaitAny

c# Task waitAll,WhenAll

Task WaitAll的用法

使用 Task.WaitAll 方法而不是 await 关键字[重复]