Await' Works, But Calling Task.Result Hangs/Deadlocks

await' works, but calling task.Result hangs/deadlocks

You're running into the standard deadlock situation that I describe on my blog and in an MSDN article: the async method is attempting to schedule its continuation onto a thread that is being blocked by the call to Result.

In this case, your SynchronizationContext is the one used by NUnit to execute async void test methods. I would try using async Task test methods instead.

Deadlock when using Result

This is not safe. The operation hasn't completed yet when you use Task.Result

When you call an async method it runs synchronously until it reaches an await and then returns to the caller with a task that represents the asynchronous operation.

The task isn't completed and using Task.Result will block the calling thread.

You should await the returned task instead, or use the synchronous option.

public static Task<bool> SuccessAsync()
{
return 0 < await ExecuteAsync("DELETE FROM Table WHERE Id = 12");
}

public static async Task<int> ExecuteAsync(string sql)
{
using (var con = Connection)
{
con.Open();
return await con.ExecuteAsync(sql);
}
}

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

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.

Why does this task cause a deadlock?

When you call GetHashFragmentAsync(input) the current synchronization context is captured by the C# async/await machinery. The method returns a started task which depends on the UI thread. You try to use Task.Run to move that task of the critical UI thread but it's too late.

GetHashFragmentAsync(input) must be called on a non-UI thread already. Wrap it in Task.Run.

Here's a helper method that works by taking a factory:

public static T GetResultSafely<T>(Func<Task<T>> task)
{
return Task.Run(() => task()).Result;
}

The factory is called on the thread pool.

I should say that normally the best solution is to use async APIs through and through, or to stay totally synchronous. Mixing is problematic for correctness and performance reasons. But it absolutely can be done safely.

Can somebody explain this deadlock behaviour

What ever GetSomethingAsync() does it is done by the calling thread until some operation (OP) can not be completed right away (for example io) then the controlflow is given to the calling function but.
If you then access the Result property on the returned Task object the thread will be blocked.

This leads to the problem that even is the OP is finished the thread will not know about it because he is busy waiting for the completion of the Task

If however you let GetSomethingAsync() be exectuted by some thread-pool thread (which Task.Run(...) does) the thread-pool thread can finish the OP and the calling thread can be notified that the Task has completed.

Update:

Your second approch does not work because the task was still started on your main thread. If you have this methode

public static async Task DoStuffAsync()
{
Console.WriteLine($"Doing some stuff1 on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(50);
Console.WriteLine($"Doing some stuff2 on thread {Thread.CurrentThread.ManagedThreadId}");
}

and run this code in an appilcation with an SynchronizationContext

var task = DoStuffAsync();
Console.WriteLine($"Doing main stuff on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Run(async () => await task);

It will output something like:

Doing some stuff1 on thread 1
Doing main stuff on thread 1
Doing some stuff2 on thread 1

So with the code line Task.Run(async () => await task) you only achieved that a thread-pool thread waits on the completion of your original Task, but this in turn creates a new Task that if not handeld by awaiting it causes a deadlock.



Related Topics



Leave a reply



Submit