我对Task和async / await模式有很好的理解,但最近有人修复了他所说的由以下原因造成的死锁:
public async Task<HttpResponseMessage> GetHttpResponse()
{
using (var client = new HttpClient())
{
return await client.SendAsync(new HttpRequestMessage()).ConfigureAwait(false);
}
}
他说他用某种Task.Factory.StartNew
模式修复了它。
public HttpResponseMessage GetHttpResponse()
{
using (var client = new HttpClient())
{
return Task.Factory.StartNew(() => client.SendAsync(new HttpRequestMessage()).Result).Result;
}
}
首先,问题是我为什么要将return语句更改为HttpResponseMessage
而不是Task<HttpResponseMessage>
。
我的第二个问题是,为什么这段代码会解决死锁错误。根据我的理解,他调用.Result
迫使GetHttpResponse
线程等待(并冻结)直到client.SendAsync
完成。
任何人都可以尝试向我解释此代码对TaskScheduler
和SynchronizationContext
的影响。
感谢您的帮助。
编辑:这是为问题提供更多上下文的调用方法
public IWebRequestResult ExecuteQuery(ITwitterQuery twitterQuery, ITwitterClientHandler handler = null)
{
HttpResponseMessage httpResponseMessage = null;
try
{
httpResponseMessage = _httpClientWebHelper.GetHttpResponse(twitterQuery, handler).Result;
var result = GetWebResultFromResponse(twitterQuery.QueryURL, httpResponseMessage);
if (!result.IsSuccessStatusCode)
{
throw _exceptionHandler.TryLogFailedWebRequestResult(result);
}
var stream = result.ResultStream;
if (stream != null)
{
var responseReader = new StreamReader(stream);
result.Response = responseReader.ReadLine();
}
return result;
}
// ...
答案 0 :(得分:1)
修复死锁的修改代码对Task APIs
的使用非常糟糕,我可以看到很多问题:
Async call to Sync call
,由于在Task
Task.Factory.StartNew
,是一个很大的问题,请 Stephen Cleary检查StartNew is Dangerous 关于您的原始代码,以下是deadlock
的最可能原因:
ConfigureAwait(false)
不起作用的原因是您需要在所有Async calls
中的完整调用堆栈中使用它,这就是它导致Deadlock
的原因。 Point为client.SendAsync
,完整的调用链需要ConfigureAwait(false)
,以便忽略Synchronization context
。只是在一个地方并没有帮助,并且无法保证不在您控制之内的电话可能的解决方案:
此处简单删除应该有效,您甚至不需要Task.WhenAll
,因为没有多个tasks
using (var client = new HttpClient())
{
return await client.SendAsync(new HttpRequestMessage());
}
另一个不太优选的选择是:
使您的代码同步如下(现在整个链中不需要ConfigureAwait(false)
):
public HttpResponseMessage GetHttpResponse()
{
using (var client = new HttpClient())
{
return await client.SendAsync(new
HttpRequestMessage()).ConfigureAwait(false).GetAwaiter().GetResult();
}
}
答案 1 :(得分:1)
return await client.SendAsync(new HttpRequestMessage()).ConfigureAwait(false);
引起的僵局并非由HttpClient.SendAsync()
引起。这是由于阻塞调用堆栈引起的。由于public async Task<HttpResponseMessage> GetHttpResponse()
的MS实现内部有一些阻塞代码(可能会死锁)非常,因此它必须是.Wait()
使用.Result
的调用者之一返回的Task上的{}或GetHttpResponse()
。你的同事所做的就是将阻塞调用移动到堆栈中,在那里它更加明显。此外,这个&#34;修复&#34;甚至没有解决经典的死锁,因为它使用相同的同步上下文(!)。你可以在堆栈的其他地方推断出一些其他函数卸载没有asp.net同步上下文的新任务,并且在新的(可能是默认的)上下文中阻塞.ConfigureAwait(false)
执行,否则&#34;修复&#34 ;会有僵局!
因为在现实世界中,并不总是可以将生产遗留代码重构为异步,所以您应该使用自己的异步HttpClient接口,并确保实现使用ControlToAdd
作为所有基础结构图书馆应该。
答案 2 :(得分:0)
@ mrinal-kamboj @ panagiotis-kanavos @shay
谢谢大家的帮助。如上所述,我已经开始阅读Async in C# 5.0 from Alex Davies
。
在第8章:哪个线程运行我的代码,他提到了我们的案例,我想我在那里找到了一个有趣的解决方案:
您可以在启动异步代码之前移动到线程池来解决死锁问题,以便捕获的 SynchronizationContext 是线程池而不是UI线程。
var result = Task.Run(() => MethodAsync()).Result;
通过调用Task.Run
,他实际上强制异步调用SynchronizationContext
为ThreadPool的SynchronizationContext
(这是有意义的)。通过这样做,他还确保代码MethodAsync
既未启动也未返回主线程。
考虑到这一点,我改变了我的代码如下:
public HttpResponseMessage GetHttpResponse()
{
using (var client = GetHttpClient())
{
return TaskEx.Run(() => client.SendAsync(new HttpRequestMessage())).Result;
}
}
此代码似乎适用于Console,WPF,WinRT和ASP.NET。我将进一步测试并更新这篇文章。
在本书中,我了解到.ConfigureAwait(false)
仅阻止回调调用SynchronizationContext.Post()
方法在调用者线程上运行。要确定应该运行回调的线程,SynchronizationContext
检查与其关联的线程是否为 important 。如果是,则选择另一个线程。
根据我的理解,这意味着回调可以在任何线程(UI-Thread或ThreadPool)上运行。因此,它不保证在UI-Thread上不执行,但使其不太可能。
有趣的是,以下代码不起作用:
public async Task<HttpResponseMessage> GetHttpResponse()
{
using (var client = GetHttpClient())
{
return await TaskEx.Run(() => client.SendAsync(new HttpRequestMessage()));
当我尝试使用此代码时,我想到.Result
可以在等待.Result
的ThreadPool范围之外使用。在某种程度上,这对我来说是有道理的,但如果有人想对此发表评论,他将受到欢迎:)