Plato Ngo
Plato Ngo

Reputation: 13

Why access Task.Result in the synchronous mode doesn't cause a deadlock?

As we all know to access the Result property of Task in the UI thread and synchronous mode will deadlock.

As theoretical follow code will deadlock but not. Can you please explain why?

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

//MVC action
public ActionResult Index()
{
      var result = System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result; // deadlock is expectation but not :(

    ...
}

I thought that System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result is simialar to GetJsonAsync(...).Result in some way, but not.

GetJsonAsync(...)).Result will deadlock but System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result is not.

Upvotes: 1

Views: 747

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456717

Result doesn't cause a deadlock by itself. It causes a deadlock when called from a single-threaded context if there is an await for that task that also needs that context.

More details:

  • await by default captures a context and resumes on that context. (You can use ConfigureAwait(false) to override this default behavior and resume on a thread pool thread instead.)
  • Result blocks the current thread until that Task is complete. (You can use await to consume a task asynchronously to avoid blocking a thread.)
  • Some contexts are single-threaded contexts; i.e., they only allow one thread in at a time. E.g., ASP.NET Classic has a single-threaded request context. (You can use Task.Run to run code on a thread pool thread, with a thread pool context, which is not a single-threaded context.)

So, to get a deadlock, you need to have an await that captures a single-threaded context, and then block a thread inside that context (e.g., calling Result on that task). The await needs the context to complete the Task, but the context only allows one thread at a time, and the Result is keeping a thread blocked in that context until the Task completes.

In your example, you're calling GetJsonAsync inside a Task.Run, which runs it on the thread pool. So the await in GetJsonAsync (and the await in the delegate passed to Task.Run) capture the thread pool context, not the ASP.NET request thread context. Then your code calls Result, which does block the ASP.NET request thread (and its context), but since the await doesn't need that context, there's no deadlock.

Upvotes: 6

Related Questions