Home > other >  What's the difference between AsyncFunction().GetAwaiter().GetResult() and Task.Run(() => As
What's the difference between AsyncFunction().GetAwaiter().GetResult() and Task.Run(() => As

Time:10-04

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.

  • Related