与 foreach 陷入僵局并等待
Posted
技术标签:
【中文标题】与 foreach 陷入僵局并等待【英文标题】:Getting deadlock with foreach and await 【发布时间】:2016-05-26 07:25:46 【问题描述】:我正在尝试调用服务,但该服务的每个请求都有最大长度,因此我将我的请求拆分为多个较小的请求。
然后我尝试将 HttpClient 与 Await 一起用作其异步
public async Task<string> CallGenoskanAsync(List<string> requestList)
ServicePointManager.ServerCertificateValidationCallback = delegate return true; ;
var credentials = new NetworkCredential(userId, password);
var tasks = new List<Task<string>>();
foreach (string requestString in requestList)
using (HttpClientHandler handler = new HttpClientHandler Credentials = credentials )
using (HttpClient client = new HttpClient(handler))
client.BaseAddress = new Uri(baseAddress);
using (HttpResponseMessage response = client.GetAsync(requestString).Result)
using (HttpContent content = response.Content)
tasks.Add(content.ReadAsStringAsync());
Task.WaitAll(tasks.ToArray());
var result = "";
foreach (var task in tasks)
result += task.Result;
return result;
此代码在调用 await client.GetAsync 时死锁,它永远不会完成。
如果我将该行更改为
using (HttpResponseMessage response = client.GetAsync(requestString).Result)
然后我没有遇到任何死锁,我想我将 await 与 foreach 一起使用不正确,但我不知道如何
编辑:更改示例代码
【问题讨论】:
【参考方案1】:编译器会为您发布的代码发出警告;特别是会指出CallGenoskanAsync
是同步的,不是异步的。
核心问题是这一行:
Task.WaitAll(tasks.ToArray());
When you're writing asynchronous code, you shouldn't block on it. To do so can cause deadlocks,正如我在博文中解释的那样。发生死锁是因为await
将捕获它用来恢复执行async
方法的“上下文”。这个“上下文”是SynchronizationContext.Current
或TaskScheduler.Current
,许多上下文(尤其是 UI 和 ASP.NET 请求上下文)只允许一个线程。因此,当您的代码阻塞线程 (Task.WaitAll
) 时,它会阻塞该上下文中的线程,这会阻止 await
继续,因为它正在等待该上下文。
要解决此问题,请让您的代码一直异步。正如我在async
介绍帖子中所解释的,the asynchronous equivalent of Task.WaitAll
is await Task.WhenAll
:
await Task.WhenAll(tasks);
WhenAll
还具有很好的属性,它可以为您解开结果,因此您不必使用有问题的Result
属性:
var results = await Task.WhenAll(tasks);
return string.Join("", results);
【讨论】:
【参考方案2】:未提供相关上下文:
如果
-
您的代码示例是返回任务的调用堆栈的一部分
在调用堆栈的根部存在阻塞等待(例如,
DoSomething().Result
或 DoSomething.Wait()
)
你有一个线程关联的SynchronizationContext
(IE,除了控制台应用程序之外,它几乎可以在任何地方运行)
那么
您的同步上下文的线程可能在根 Result/Wait() 上被阻塞,因此它永远无法处理已完成的 GetAsync() 调用调度给它的延续。死锁。
如果您满足上述条件,请尝试将 .ConfigureAwait(false) 应用于您等待的 GetAsync。这将使继续被安排到线程池线程。请注意,此时您可能会被安排到不同的线程。
【讨论】:
以上是关于与 foreach 陷入僵局并等待的主要内容,如果未能解决你的问题,请参考以下文章
当我期望它在 forEach 中时,为啥 node.js 不等待? [复制]
在 foreach 中等待几个可观察的 rxjs 直到完成执行另一个