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

时间:2021-11-18 04:13:38

I'm hitting deadlock even after using ConfigureAwait(false), below is the sample code.

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

As per the sample http://blog.stephencleary.com/2012/02/async-and-await.html (#Avoding Context), this should not have hit dead lock.

根据示例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:

如果我在共享库中添加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.
        ...
}

I've been going through all blogs found, only difference I find is ConfigureAwait(false) works when used with httpClient.AsyncApi() call!?

我一直在浏览所有发现的博客,但我发现只有在与httpClient.AsyncApi()调用一起使用时才会发现ConfigureAwait(false)有效!

Please help clarify!!!

请帮忙澄清!!!

3 个解决方案

#1


15  

From the comments:

来自评论:

I was under assumption, once ConfigureAwait(false) is used (any where in the call stack), execution from that point will not cause deadlock.

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

I don't believe in black magic, and neither should you. Always strive to understand what happens when you use something in your code.

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

When you await an async method that returns a Task or a Task<T>, there is an implicit capture of the SynchronizationContext by the TaskAwaitable being generated by the Task.GetAwaiter method.

当您等待返回Task或Task 的异步方法时,Task.WetAwaiter方法生成的TaskAwaitable会隐式捕获SynchronizationContext。

Once that sync context is in place and the async method call completes, the TaskAwaitable attempts to marshal the continuation (which is basically the rest of the method calls after the first await keyword) onto the SynchronizationContext (using SynchronizationContext.Post) which was previously captured. If the calling thread is blocked, waiting on that same method to finish, you have a deadlock.

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

You should ask yourself Should I expose synchronous wrappers for asynchronous methods? 99 percent of the time the answer is no. You should use a synchronous API, such as the one WebClient offers.

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

#2


7  

It blocks when used in ProjectsRetriever because:

它在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;
        ...
}

It does not block when used in ProjectSystem because:

它在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
}

#3


0  

I had the same problem. "ConfigureAwait(false)" can not always avoid dead lock.

我有同样的问题。 “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();
    }
}

For the above code, "GetAsync()" works while "PingAsync()" doesn't.

对于上面的代码,“GetAsync()”有效,而“PingAsync()”则无效。

But I found that if I wrap the async call into a new task, and wait this task, PingAsync() will work event without "ConfigureAwait(false)":

但我发现,如果我将异步调用包装到一个新任务中,并等待此任务,PingAsync()将在没有“ConfigureAwait(false)”的情况下运行事件:

var task = Task.Run(() => PingAsync());
task.Wait();
ViewBag.Title = task.Result;

I don't know the reason, maybe someone can tell me the difference.

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

#1


15  

From the comments:

来自评论:

I was under assumption, once ConfigureAwait(false) is used (any where in the call stack), execution from that point will not cause deadlock.

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

I don't believe in black magic, and neither should you. Always strive to understand what happens when you use something in your code.

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

When you await an async method that returns a Task or a Task<T>, there is an implicit capture of the SynchronizationContext by the TaskAwaitable being generated by the Task.GetAwaiter method.

当您等待返回Task或Task 的异步方法时,Task.WetAwaiter方法生成的TaskAwaitable会隐式捕获SynchronizationContext。

Once that sync context is in place and the async method call completes, the TaskAwaitable attempts to marshal the continuation (which is basically the rest of the method calls after the first await keyword) onto the SynchronizationContext (using SynchronizationContext.Post) which was previously captured. If the calling thread is blocked, waiting on that same method to finish, you have a deadlock.

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

You should ask yourself Should I expose synchronous wrappers for asynchronous methods? 99 percent of the time the answer is no. You should use a synchronous API, such as the one WebClient offers.

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

#2


7  

It blocks when used in ProjectsRetriever because:

它在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;
        ...
}

It does not block when used in ProjectSystem because:

它在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
}

#3


0  

I had the same problem. "ConfigureAwait(false)" can not always avoid dead lock.

我有同样的问题。 “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();
    }
}

For the above code, "GetAsync()" works while "PingAsync()" doesn't.

对于上面的代码,“GetAsync()”有效,而“PingAsync()”则无效。

But I found that if I wrap the async call into a new task, and wait this task, PingAsync() will work event without "ConfigureAwait(false)":

但我发现,如果我将异步调用包装到一个新任务中,并等待此任务,PingAsync()将在没有“ConfigureAwait(false)”的情况下运行事件:

var task = Task.Run(() => PingAsync());
task.Wait();
ViewBag.Title = task.Result;

I don't know the reason, maybe someone can tell me the difference.

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