13
votes

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

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

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

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

Please help clarify!!!

3
Your title says *"it doesn't work even when using ConfigureAwait(false) but in your code you say the second example works. Which one is it?Yuval Itzchakov
@Yuval Itzchakov: it does not work when used in ProjectsRetriever and works when used in ProjectSystemKhanh TO
@user2746890: I was under assumption, once ConfigureAwait(false) is used (any where in the call stack), execution from that point will not cause deadlock. It won't capture the context for that await. But break out your invocations and awaits, and you'll find that ProjectSystem.GetProjects is invoked (and awaits) before you call ConfigureAwait(false) on the task returned by GetProjects. IMO the best answer is "only provide an asynchronous API", i.e., make ProjectsRetriever.GetProjects() async.Stephen Cleary
I understand it now. Thanks very much for clarifying Stephen/Khanh/Yuval!Suresh Tadisetty
I do not understand this post. GetProjects() (the overload with no parameters) is not async and does not return a Task, so you cannot use await on it. This code is not even valid.BlueRaja - Danny Pflughoeft

3 Answers

18
votes

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.

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.

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.

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.

7
votes

It blocks when used in ProjectsRetriever because:

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:

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
}
1
votes

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

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.

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)":

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

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