C# AsParallel 在调用 HttpClient 时被阻塞

C# AsParallel gets blocked when calling HttpClient

有这个 question 提到使用 async/await 来防止死锁。 在我的例子中,此代码仅适用于 elementsInList 变量中的 2 或 3 个元素,如下所示。 我想知道是否有数量因素会影响僵局的解除。

我正在尝试使用以下代码块:

var responses = elementsInList.AsParallel()
                    .WithDegreeOfParallelism(elementsInList.Count)
                    .Select(elementInList => GetResponse(elementInList, timeout)).ToList();

(...)

public JObject GetResponse(JObject request, TimeSpan timeout)
        {
              var endpoint = ConfigurationManager.AppSettings.Get("MyEndPoint");

                try
                {
                    using (var client = new HttpClient())
                    {
                        client.DefaultRequestHeaders.Accept.Clear();
                        client.Timeout = timeout;

                        IList<MediaTypeFormatter> formatters = GetMediaTypeFormatters();
                        HttpResponseMessage response = client.PostAsJsonAsync(EndPoint, request).Result;

                        if (response.IsSuccessStatusCode)
                        {
                            return response.Content.ReadAsAsync<T>(formatters).Result;
                        }

                        throw new Exception(response.ReasonPhrase);
                    }

                }
                catch (Exception ex)
                {
                    return new JObject()
                    {
                        new JProperty("Exception", "Invalid response. " + ex.Message)
                    };
                }
        } 

如果我调试代码,当 .AsParallel 行被命中时,代码会被阻塞,它永远不会 returns 回答或转到下一行。 那里似乎发生了僵局。

我想补充一个细节,如果我将此代码与 2 或 3 个元素一起使用,它工作正常,但是,当调用大约 30-50 个元素时,它会失败。是否有一个数量因素可以在第二种情况下而不是第一种情况下引发僵局?我很好奇它是否会影响,或者它是否也可以只用 2 或 3 个元素生成。

作为一般经验法则,仅当您的应用需要执行 CPU 密集型工作时才使用并行技术。对于 I/O-bound 工作,使用异步技术。对于需要并行和异步代码的极其罕见情况,请考虑像 TPL Dataflow 这样的技术。

在你的情况下,没有 CPU 密集的工作要做,所以删除所有并行的东西,只使用 async/await 代替:

public async Task<JObject> GetResponseAsync(JObject request, TimeSpan timeout)
{
  var endpoint = ConfigurationManager.AppSettings.Get("MyEndPoint");
  try
  {
    using (var client = new HttpClient())
    {
      client.DefaultRequestHeaders.Accept.Clear();
      client.Timeout = timeout;

      IList<MediaTypeFormatter> formatters = GetMediaTypeFormatters();
      HttpResponseMessage response = await client.PostAsJsonAsync(EndPoint, request);

      if (response.IsSuccessStatusCode)
        return await response.Content.ReadAsAsync<T>(formatters);

      throw new Exception(response.ReasonPhrase);
    }
  }
  catch (Exception ex)
  {
    return new JObject()
    {
      new JProperty("Exception", "Invalid response. " + ex.Message)
    };
  }
}

用法:

var responses = await Task.WhenAll(
    elementsInList.Select(x => GetResponseAsync(x, timeout)));