为什么Task.Run(()=>某事)会调用等待死锁的同步方法?

时间:2015-11-21 23:51:06

标签: c# async-await task-parallel-library deadlock

我正在试图找出我正在使用WebRequest / WebResponse转换为HttpClient的TPL程序中发生死锁的位置。

我有一个创建100个任务的循环并将它们放在一个数组中:

var putTasks = new Task[100];
for (var i = 0; i < 100; i++)
{
    putTasks[i] = Task.Run(() => client.Put("something"));
}
Task.WaitAll(putTasks);

代码挂在Task.WaitAll上,查看服务器端显示第一个请求完成,但其余请求没有完成。

关于这一点的奇怪部分(是的,它应该是异步的,但它现在不是,而且我试图理解为什么它在我完全异步之前它的死锁)是client.Put是一个看起来像这样的同步方法:

string Put(string thing) { return PutAsync(string thing).GetAwaiter().GetResult(); }

和PutAsync看起来像:

async Task<string> PutAsync(string thing)
{
    //httpClient built up here
    HttpContent content = new StringContent(thing);

    var response = await httpClient.PutAsync("http://myuri", content);
    var result = await response.Content.ReadAsStreamAsync();
    // Do some stuff
}

PutAsync内部悬挂的部分是response = await httpClient.PutAsync。我在UI线程中看到了一堆关于死锁的答案,但没有任何修复(例如更改捕获的上下文)有帮助。

理想情况下,我只是让所有的异步一直向下并等待一切,但正如我所说,我试图弄清楚为什么会发生僵局以及在我撕裂一切之前发生的事情 - 另一个问题是这是其他人使用的库,接口是同步方法,因此我不能只将其更改为异步并更新一堆隐藏代码。

2 个答案:

答案 0 :(得分:3)

  

那么请发一个答案,让我了解如何使用提供同步方法的API(例如client.Put(string thing)方法,并将其连接到幕后的异步方法。

You don't. Doing so doesn't provide any benefit.只有提供图书馆的人才应该打包他们知道的事件call some type of Completion Port。创建一个单独的线程(Task.Run())来等待资源只是浪费资源(有例外)。

  

我无法将其更改为异步并更新一堆隐藏代码。

你不应该。

  

它在PutAsync内部悬挂的部分是response = await httpClient.PutAsync

导致99%的死锁是因为来电MethodAsync()的行为不正确,没有特定的代码,知道为什么无法回答。

仅在您知道使用完成端口

时才有用

我无法演示PutAsync,因为在HttpClient上无法创建真正的异步方法。相反,我会为ExecuteNonQuery()创建SqlCommand

public static Task<int> ExecuteNonQueryAsync(this SqlCommand sqlCommand)
{
  var tcs = new TaskCompletionSource<int>();

  sqlCommand.BeginExecutedNonQuery(result =>
  {
    tcs.SetResult(sqlCommand.EndExecutedNonQuery());
  });

  return await tcs.Task;
}

当然,这是非常糟糕的编码,没有尝试/捕获等,但它没有使用Task.Run()来启动另一个什么都不做的线程。

现在,如果您想要使用异步任务正确执行并行任务调用,那么这样的事情更合适(并且不会浪费线程):

public async Task<IEnumerable<HttpResponseMessage>> GoCrazy()
{
  var putTasks = new List<Task<HttpResponseMessage>>();

  for (var i = 0; i < 100; i++)
  {
    var task = client.PutAsync("something");
    putTasks.Add(task);
  }

  // WhenAll not WaitAll
  var result = await Task.WhenAll(putTasks);

  return result;
}

答案 1 :(得分:2)

问题是调用同步API填满线程池的所有正在运行的任务,以便异步调用HttpClient必须做的没有线程可以运行。