我正在试图找出我正在使用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线程中看到了一堆关于死锁的答案,但没有任何修复(例如更改捕获的上下文)有帮助。
理想情况下,我只是让所有的异步一直向下并等待一切,但正如我所说,我试图弄清楚为什么会发生僵局以及在我撕裂一切之前发生的事情 - 另一个问题是这是其他人使用的库,接口是同步方法,因此我不能只将其更改为异步并更新一堆隐藏代码。
答案 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必须做的没有线程可以运行。