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 useConfigureAwait(false)
to override this default behavior and resume on a thread pool thread instead.)Result
blocks the current thread until thatTask
is complete. (You can useawait
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
Difference Between a Reference Type and Value Type in C#
How to Auto-Generate a C# Class File from a Json String
Return Multiple Values to a Method Caller
Returning Ienumerable≪T≫ Vs. Iqueryable≪T≫
How to Convert a Column Number (E.G. 127) into an Excel Column (E.G. Aa)
C# Datetime to "Yyyymmddhhmmss" Format
What Are Regular Expression Balancing Groups
How to Convert a Byte Array to a Hexadecimal String, and Vice Versa
How to Enable Assembly Bind Failure Logging (Fusion) in .Net
How to Make a Textbox That Only Accepts Numbers
Send Values from One Form to Another Form
Creating a Byte Array from a Stream