与 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.CurrentTaskScheduler.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().ResultDoSomething.Wait()) 你有一个线程关联的SynchronizationContext(IE,除了控制台应用程序之外,它几乎可以在任何地方运行)

那么

您的同步上下文的线程可能在根 Result/Wait() 上被阻塞,因此它永远无法处理已完成的 GetAsync() 调用调度给它的延续。死锁。

如果您满足上述条件,请尝试将 .ConfigureAwait(false) 应用于您等待的 GetAsync。这将使继续被安排到线程池线程。请注意,此时您可能会被安排到不同的线程。

【讨论】:

以上是关于与 foreach 陷入僵局并等待的主要内容,如果未能解决你的问题,请参考以下文章

等待 forEach 循环

当我期望它在 forEach 中时,为啥 node.js 不等待? [复制]

在 foreach 中等待几个可观察的 rxjs 直到完成执行另一个

NodeJS:等待所有带有 Promises 的 foreach 完成,但从未真正完成

在 Parallel.foreach 中等待 [重复]

等待内部带有承诺的 forEach 完成