与foreach一起陷入僵局并等待着

时间:2016-05-26 07:25:46

标签: c# asynchronous dotnet-httpclient

我正在尝试调用某个服务,但该服务的每个请求的最大长度为,因此我将请求拆分为多个较小的请求。

然后我尝试将HttpClient与Await一起用作async

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)

然后我没有遇到任何僵局,我想我正在和foreach一起使用等待,但我无法弄清楚如何

编辑:更改了示例代码

2 个答案:

答案 0 :(得分: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将捕获&#34; context&#34;它用于恢复执行async方法。这个&#34;上下文&#34;是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);

答案 1 :(得分:0)

未提供相关背景:

如果

  1. 您的代码示例是返回任务
  2. 的callstack的一部分
  3. 在callstack的根处有一个阻塞等待(例如, DoSomething().ResultDoSomething.Wait()
  4. 你有一个SynchronizationContext的线程关联(IE,这几乎在任何地方运行,但控制台应用程序)
  5. ,然后

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

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