13

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!!!

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Suresh Tadisetty
  • 300
  • 1
  • 2
  • 8
  • 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 Aug 31 '14 at 03:50
  • @Yuval Itzchakov: it does not work when used in `ProjectsRetriever` and works when used in `ProjectSystem` – Khanh TO Aug 31 '14 at 03:53
  • 4
    @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 Aug 31 '14 at 10:36
  • I understand it now. Thanks very much for clarifying Stephen/Khanh/Yuval! – Suresh Tadisetty Sep 01 '14 at 05:41
  • 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 Jun 30 '15 at 20:57
  • @BlueRaja, This is a sample code. – Suresh Tadisetty Jul 02 '15 at 04:32
  • But it's the part of the sample code attempting to explain the problem. You claim to be having issues `await`ing a synchronous method. If the method is synchronous, you cannot `await` it, that's a compiler error. If the method is actually asynchronous, then you should not be using `.Result` to begin with, call `await` instead. The question makes no sense. – BlueRaja - Danny Pflughoeft Jul 02 '15 at 05:52
  • Sorry, I need to understand your question better. yes, GetProjects() is not async. Where did I use it as async method? And regarding compiler errors, This is a valid code I've and just renamed class names. Let me know if it failed for you. – Suresh Tadisetty Jul 04 '15 at 04:35

3 Answers3

18

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 2
    I have created a library that can easily plugged into your ASP.NET application to detect these deadlocks and help you track them down. More information on https://github.com/ramondeklein/deadlockdetection. – Ramon de Klein Jan 19 '17 at 10:37
  • I could feel your confidence by reading your answer. well answered. – johni Aug 10 '17 at 21:20
  • I'm late to the party, but the article URL was updated to https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ – Yann Oct 07 '22 at 13:27
7

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
}
Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • sorry, that's the part confusing me. Is it because HttpClient is making rest call (execution is transferred, not happen in the same context)? – Suresh Tadisetty Aug 31 '14 at 04:31
  • @user2746890: which part confuses you? In the first example, you call `await projectClient.GetProjects()` with Thread1, so Thread1 must continue the execution but it's BLOCKED in `this.GetProjects(uri).Result;` – Khanh TO Aug 31 '14 at 04:37
  • I was under assumption, once ConfigureAwait(false) is used (any where in the call stack), execution from that point will not cause deadlock. – Suresh Tadisetty Aug 31 '14 at 04:41
  • @user2746890: `ConfigureAwait(false)` only states that any thread other than the awaiting thread can continue the code. – Khanh TO Aug 31 '14 at 04:44
  • Calling `Task.Result` will block the calling thread until the task is completed no matter what thread the task is running (or if it is even running) at the moment. – Paulo Morgado Aug 31 '14 at 23:27
1

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.

Meng Xue
  • 491
  • 5
  • 7