【问题标题】:deadlock even after using ConfigureAwait(false) in Asp.Net flow即使在 Asp.Net 流程中使用 ConfigureAwait(false) 后也会出现死锁
【发布时间】:2014-08-31 03:18:14
【问题描述】:

即使在使用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() 调用一起使用时有效!?

请帮忙澄清!!!

【问题讨论】:

  • 您的标题显示 *"即使使用 ConfigureAwait(false),它也不起作用,但在您的代码中,您说第二个示例有效。它是哪一个?
  • @Yuval Itzchakov:在ProjectsRetriever 中使用时不起作用,在ProjectSystem 中使用时起作用
  • @user2746890: I was under assumption, once ConfigureAwait(false) is used (any where in the call stack), execution from that point will not cause deadlock. 它不会捕获等待的上下文。但是打破你的调用和等待,你会发现ProjectSystem.GetProjectsGetProjects返回的任务上调用ConfigureAwait(false)之前被调用(并等待)。 IMO 的最佳答案是“仅提供异步 API”,即使 ProjectsRetriever.GetProjects() 异步。
  • 我现在明白了。非常感谢您澄清 Stephen/Khanh/Yuval!
  • 我不明白这篇文章。 GetProjects() (没有参数的重载) 不是async 并且不返回Task,所以你不能在它上面使用await。此代码甚至无效。

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


【解决方案1】:

来自cmets:

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

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

当您 await 一个返回 TaskTask&lt;T&gt; 的异步方法时,TaskAwaitableTask.GetAwaiter 方法生成的 SynchronizationContext 隐式捕获。

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

您应该问自己Should I expose synchronous wrappers for asynchronous methods? 99% 的答案是。您应该使用同步 API,例如 WebClient 提供的 API。

【讨论】:

  • 我创建了一个库,可以轻松插入到您的 ASP.NET 应用程序中,以检测这些死锁并帮助您跟踪它们。有关github.com/ramondeklein/deadlockdetection 的更多信息。
  • 阅读您的回答,我能感受到您的信心。很好的回答。
【解决方案2】:

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
}

【讨论】:

  • 抱歉,这部分让我感到困惑。是不是因为 HttpClient 正在进行休息调用(执行转移,不在同一个上下文中发生)?
  • @user2746890:哪一部分让你感到困惑?在第一个示例中,您使用 Thread1 调用 await projectClient.GetProjects(),因此 Thread1 必须继续执行,但它在 this.GetProjects(uri).Result; 中被 BLOCKED
  • 我假设,一旦使用 ConfigureAwait(false)(调用堆栈中的任何位置),从该点执行不会导致死锁。
  • @user2746890: ConfigureAwait(false) 只声明等待线程以外的任何线程都可以继续执行代码。
  • 调用Task&lt;T&gt;.Result 将阻塞调用线程直到任务完成,无论当前任务正在运行(或者甚至正在运行)的线程。
【解决方案3】:

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

我不知道原因,也许有人可以告诉我区别。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    • 1970-01-01
    • 2017-06-21
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多