I try to understand the concept between GetAwaiter().GetResult()
. I know that it shouldn't be used if possible but I cannot use async/await in this case so I must get the result from an asynchronous function in a synchronous matter.
Let's consider this simple Blazor server app:
@page "/"
<button @onclick="GetAwaiterGetResultOnTask">Task GetAwaiter().GetResult()</button>
<button @onclick="GetAwaiterGetResultOnFunction">Function GetAwaiter().GetResult()</button>
<p>@_message</p>
@code {
private string _message;
private async Task<string> AsyncFunction(string message)
{
await Task.Delay(500);
return message;
}
private void GetAwaiterGetResultOnTask()
{
_message = Task.Run(() => AsyncFunction("Message from GetAwaiterGetResultOnTask")).GetAwaiter().GetResult();
}
private void GetAwaiterGetResultOnFunction()
{
_message = AsyncFunction("Message from GetAwaiterGetResultOnFunction").GetAwaiter().GetResult();
}
}
Calling the function GetAwaiterGetResultOnFunction
will result in a deadlock. However when I call GetAwaiterGetResultOnTask
it does not.
What is the key difference between those to functions? Why does it not results in a deadlock when called on Task.Run
?
CodePudding user response:
await
is aware of a thing called SynchronizationContext
. If such context is present (SynchronizationContext.Current
is not null) then continuation after await
is passed to that context, because it should know better how to handle it (unless you explicitly tell to not do that, using await someTask.ConfigureAwait(continueOnCapturedContext: false)
)
Most UI frameworks do not like when UI is accessed from multiple threads concurrently, because it often leads to various hard to debug problems. They utilize SynchronizationContext
to enforce single flow of execution. To do that - they usually put callbacks posted to SynchronizationContext in some sort of queue, and execute them one by one on the single "UI" thread.
Blazor also does that. GetAwaiterGetResultOnFunction
is your case is executed on that "UI" thread, with SynchronizationContext
available. When execution reaches await Task.Delay
inside AsyncFunction
- current context is captured and it's noted that when Task.Delay
completes - the rest of the function should be posted to the context for execution.
Then you block the "UI" thread by doing GetResult
on a task returned from AsyncFunction
. Then Task.Delay
completes and the rest of the function is posted to the SynchronizationContext
of Blazor to execute. However, to do that Blazor needs the same UI thread you just blocked, resulting in deadlock.
When you call GetAwaiterGetResultOnTask
- AsyncFunction
does not run on "UI" thread of Blazor - it runs on some other (thread pool) thread. SynchronizationContext
is null, the part after await Task.Delay
will run on some thread pool thread and will not require "UI" thread to complete. Then there is no deadlock.