即使使用ConfigureAwait(false)
后我也遇到了死锁,下面是示例代码。
根据示例http://blog.stephencleary.com/2012/02/async-and-await.html(#Avoding Context),这不应该命中死锁。
这是我的班级:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
此课程来自共享库:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit
...
}
如果我在共享库中添加ConfigureAwait(false)来调用HttpClient进行调用,则可以正常工作:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread.
...
}
我一直在浏览所有发现的博客,但我发现只有在与httpClient.AsyncApi()调用一起使用时才会发现ConfigureAwait(false)有效!
请帮助澄清!!!
答案 0 :(得分:16)
来自评论:
我假设,一旦使用了ConfigureAwait(false)(调用堆栈中的任何位置),从该点开始执行不会导致死锁。
我不相信黑魔法,你也不应该。始终努力了解在代码中使用某些内容时会发生什么。
当您await
返回Task
或Task<T>
的异步方法时,生成的SynchronizationContext
会隐式捕获TaskAwaitable
通过Task.GetAwaiter
方法。
一旦该同步上下文到位并且异步方法调用完成,TaskAwaitable
会尝试将延续(基本上是第一个await
关键字之后的其余方法调用)编组到先前捕获的SynchronizationContext
(使用SynchronizationContext.Post
)。如果调用线程被阻塞,等待相同的方法完成,则会出现死锁。
你应该问自己Should I expose synchronous wrappers for asynchronous methods? 99%的时间答案是不。您应该使用同步API,例如一个WebClient
商品。
答案 1 :(得分:7)
在ProjectsRetriever
中使用时会阻止,因为:
public class ProjectsRetriever
{
public string GetProjects()
{
//querying the result blocks the thread and wait for result.
var projects = this.GetProjects(uri).Result;
... //require Thread1 to continue.
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//any thread can continue the method to return result because we use ConfigureAwait(false)
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit because it requires Thread1 to continue its execution
// but Thread1 is blocked in var projects = this.GetProjects(uri).Result;
...
}
在ProjectSystem
中使用时不会阻止,因为:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...//requires Thread1 to continue
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//requires Thread1 to continue
return await this.projectSystem.GetProjects(uri, Constants.UserName);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread. After this function returns, Thread1 could continue to run
}
答案 2 :(得分:0)
我有同样的问题。 “ ConfigureAwait(false)”不能总是避免死锁。
public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
// This works !
ViewBag.Title = GetAsync().Result;
// This cause deadlock even with "ConfigureAwait(false)" !
ViewBag.Title = PingAsync().Result;
return View();
}
public async Task<string> GetAsync()
{
var uri = new Uri("http://www.google.com");
return await new HttpClient().GetStringAsync(uri).ConfigureAwait(false);
}
public async Task<string> PingAsync()
{
var pingResult = await new Ping().SendPingAsync("www.google.com", 3).ConfigureAwait(false);
return pingResult.RoundtripTime.ToString();
}
}
对于上述代码,“ GetAsync()”有效,而“ PingAsync()”无效。
但是我发现,如果将异步调用包装到一个新任务中,然后等待该任务,PingAsync()将在没有“ ConfigureAwait(false)”的情况下工作:
var task = Task.Run(() => PingAsync());
task.Wait();
ViewBag.Title = task.Result;
我不知道原因,也许有人可以告诉我与众不同。