首页 文章

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

提问于
浏览
12

即使在使用 ConfigureAwait(false) 之后我也遇到了死锁,下面是示例代码 .

根据示例http://blog.stephencleary.com/2012/02/async-and-await.html(#Avoding Context),这不应该是命中死锁 .

This is my class

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);
    }
}

This class is from a shared library:

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
        ...
}

Works if I add ConfigureAwait(false) to await call in shared library, where HttpClient call is made:

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)有效!

Please help clarify!!!

3 回答

  • 16

    来自评论:

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

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

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

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

    你应该问自己Should I expose synchronous wrappers for asynchronous methods?百分之99的答案是 no . 您应该使用同步API,例如一个 WebClient 商品 .

  • 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
    }
    
  • 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;
    

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

相关问题