死锁,即使在Asp.Net流中使用ConfigureAwait(false)之后也是如此

本文关键字:false 之后 ConfigureAwait Asp Net 死锁 | 更新日期: 2023-09-27 18:20:03

即使使用了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.
        ...
}

我已经浏览了所有找到的博客,我发现的唯一区别是ConfigureAwait(false)在与httpClient.AsyncApi()调用一起使用时有效!?

请帮忙澄清

死锁,即使在Asp.Net流中使用ConfigureAwait(false)之后也是如此

来自注释:

我假设,一旦使用ConfigureAwait(false)(调用堆栈中的任意位置),从该点执行不会导致死锁。

我不相信黑魔法,你也不应该。始终努力理解当您在代码中使用某些内容时会发生什么。

await是一个返回TaskTask<T>的异步方法时,Task.GetAwaiter方法生成的TaskAwaitable会隐式捕获SynchronizationContext

一旦同步上下文就位并且异步方法调用完成,TaskAwaitable就尝试将延续(基本上是第一个await关键字之后的方法调用的其余部分)封送到先前捕获的SynchronizationContext(使用SynchronizationContext.Post)上。如果调用线程被阻塞,等待同一方法完成,则会出现死锁

您应该问问自己我应该为异步方法公开同步包装器吗99%的情况下,答案是。您应该使用同步的API,例如WebClient提供的API。

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
}

我也遇到了同样的问题。"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;

我不知道原因,也许有人能告诉我其中的区别。