【问题标题】:Getting deadlock with foreach and await与 foreach 陷入僵局并等待
【发布时间】: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 一起使用不正确,但我不知道如何

编辑:更改示例代码

【问题讨论】:

    标签: c# asynchronous dotnet-httpclient


    【解决方案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】:

      未提供相关上下文:

      如果

      1. 您的代码示例是返回任务的调用堆栈的一部分
      2. 在调用堆栈的根部存在阻塞等待(例如, DoSomething().ResultDoSomething.Wait())
      3. 你有一个线程关联的SynchronizationContext(IE,除了控制台应用程序之外,它几乎可以在任何地方运行)

      那么

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

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

      【讨论】:

        猜你喜欢
        • 2019-04-28
        • 1970-01-01
        • 2022-07-14
        • 1970-01-01
        • 1970-01-01
        • 2017-10-06
        • 2020-04-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多