Is .Getawaiter().Getresult(); Safe for General Use

Is .GetAwaiter().GetResult(); safe for general use?

As I describe on my blog, GetAwaiter().GetResult() can deadlock when it's used in a one-thread-at-a-time context. This is most commonly seen when called on the UI thread or in an ASP.NET context (for pre-Core ASP.NET).

Wait has the same problems. The appropriate fix is to use await, and make the calling code asynchronous.

Note that the Main method in Console apps is an exception to this rule; it is perfectly appropriate to use there. Many code samples use it in this way.

When to use Task.Run().GetAwaiter().GetResult() and ().GetAwaiter.GetResult()?

When you use Task.Run, the initial synchronous part of your delegate is run on a threadpool thread, whereas just ().GetAwaiter().GetResult() will run that synchronous part on the same thread.

Using Task.Run(...).GetAwaiter().GetResult() can be used as a workaround to run async code and wait on it synchronously, it will not result in an async deadlock, whereas ().GetAwaiter().GetResult() could. Do be aware that it still isn't "safe", in that you likely are blocking within a threadpool thread, on servers this can lead to thread pool exhaustion at load.

If you want to run a Task returning method, and know that the initial synchronous part is trivial, and you know that the rest of the async method will not run with a SynchronizationContext, just ().GetAwaiter().GetResult() can be a micro-optimization, I'd say only do it if you know exactly what you are doing.

How do you know that you are running under no SynchronizationContext? SynchronizationContext.Current will be null, due to one the following reasons:

  • You know your code is running under an application model that doesn't have one (Console app, ASP.NET Core, Windows Service)
  • You have used .ConfigureAwait(false) on an awaited incomplete Task previously in the current stack.
  • You have explicitly called SynchronizationContext.SetSynchronizationContext(null)

So you see, it's a lot to think about, so in general you almost always want to use Task.Run(...).GetAwaiter.GetResult().

Neither GetAwaiter().GetResult() nor .Result worked for me but Task.Run(await ()= nameOfMethodTobeCalled()).Result working. I didn't understand?

So for that I called those method and to retrieve result I used

A better solution is to use await and allow the asynchrony to grow naturally through the code base. If you do have to do sync-over-async, then direct blocking like that may cause deadlocks.

await captures a context by default, and in your case the await inside of MyMethod is capturing an ASP.NET request context, which existed on pre-Core versions of ASP.NET. Then, the calling code blocks a thread in that request context by calling GetResult()/Result. Later, when the await is ready to continue, it schedules the remainder of MyMethod to that context. But it will never be run because there's a thread blocked in that context waiting for MyMethod to complete.

The reason Task.Run doesn't deadlock is because MyMethod uses the thread pool context instead of an ASP.NET request context. This is the "thread pool hack" approach to doing sync-over-async. However, Task.Run is not recommended on ASP.NET; if possible, change the code to use await instead.

What's the difference between AsyncFunction().GetAwaiter().GetResult() and Task.Run(() = AsyncFunction).GetAwaiter().GetResult()?

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.

Why in one case GetAwaiter() block my application and in another application is not blocked?

OK, figured out. The deadlock is becuase of SynchronizationContext and it's capturing as stated many times in comments to your question.
Usually solution is either use await all the way up, or ConfigureAwait(false), which you doing. BUT!

As stated here (section The Blocking Hack):
https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development

If somewhere down the line Microsoft/EF uses conversion from old event-based async pattern then ConfigureAwait(false) will have no effect, cause context will be already captured anyway.

And turns out they do such conversion, here is the source of SqlCommand for SqlServer:

https://github.com/dotnet/SqlClient/blob/bd89b6433892214eed41e97afb0a9430d11d4681/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs

Look there for line 2996.

Now why SqlLite is working fine - because SqlLite doesn't not support async I/O, so all asyncs for SqlLite are fake, you could clearly see it in source of SqliteCommand:

https://github.com/dotnet/efcore/blob/main/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs

Line 406.

And explanation for "fake" is here - https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/async

Not getting past GetAwaiter().GetResult()

You're experiencing the common deadlock that happens when you block on asynchronous code (described in detail on my blog). There are a variety of ways to get around it, but they're all hacks and none of them work in every situation.

In your case, I'd say either use the direct blocking hack or use the boolean argument hack.

The direct blocking hack requires you to use ConfigureAwait(false) everywhere. Note that your current code only uses ConfigureAwait(false) where it doesn't do anything; ConfigureAwait configures the await, so it needs to go where the awaits are. All of them.

The boolean argument hack means that your code will take a bool parameter that determines whether it executes synchronously or asynchronously. Note that HttpClient (for now) has an async-only API, so your custom delegating handler will need to support direct blocking, using ConfigureAwait(false). Similarly, Log will either need a synchronous equivalent or also support direct blocking. Your code would end up looking something like this:

public Task<TResponse> PostAsync<TResponse, TRequest>(string method, TRequest body) => PostCoreAsync(method, body, sync: false);
public TResponse Post<TResponse, TRequest>(string method, TRequest body) => PostCoreAsync(method, body, sync: true).GetAwaiter().GetResult();
private async Task<TResponse> PostCoreAsync<TResponse, TRequest>(string method, TRequest body, bool sync)
{
_logger.Log($"Method {method}, body {JsonConvert.SerializeObject(body)} on url {_configuration.ApiUrl}");
using (var customDelegatingHandler = new HMACDelegatingHandler(_configuration, _apiId))
{
using (var client = new HttpClient(customDelegatingHandler))
{
var responseTask = client.PostAsync($"{_configuration.ApiUrl}/{method}",
new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));
var response = sync ? responseTask.GetAwaiter().GetResult() : await responseTask;

if (response.StatusCode == HttpStatusCode.OK)
{
var content = sync ? response.Content.ReadAsStringAsync().GetAwaiter().GetResult() : await response.Content.ReadAsStringAsync();

return JsonConvert.DeserializeObject<TResponse>(content);
}
else
{
var logTask = Log(body, response);
if (sync)
logTask.GetAwaiter().GetResult();
else
await logTask;
}
return default;
}
}
}

public Task<decimal> GetBalanceAsync(Request request) => GetBalanceCoreAsync(request, sync: false);
public decimal GetBalance(Request request) => GetBalanceCoreAsync(request, sync: true).GetAwaiter().GetResult();
private async Task<decimal> GetBalanceCoreAsync(Request request, bool sync)
{
// = new MyCustomClient...

QueryFundsResponse response = sync ?
customClient.Post<Response, Request>("testAction", request) :
await customClient.PostAsync<Response, Request>("testAction", request);
if (response == default)
return 0.0m;

return response.Amount;
}

var sw = new Stopwatch();
sw.Start();
var balance = _provider
.GetBalance(request);
sw.Stop();
_logger.Log($"GetBalance -> method duration: { sw.ElapsedMilliseconds }");

What is the difference between .Wait() vs .GetAwaiter().GetResult()?

Both are a synchronous wait for the result of the operation (and you should avoid those if possible).

The difference is mainly in handling exceptions. With Wait, the exception stack trace is unaltered and represents the actual stack at the time of the exception, so if you have a piece of code that runs on a thread-pool thread, you'd have a stack like

ThreadPoolThread.RunTask
YourCode.SomeWork

On the other hand, .GetAwaiter().GetResult() will rework the stack trace to take all the asynchronous context into account, ignoring that some parts of the code execute on the UI thread, and some on a ThreadPool thread, and some are simply asynchronous I/O. So your stack trace will reflect a synchronous-like step through your code:

TheSyncMethodThatWaitsForTheAsyncMethod
YourCode.SomeAsyncMethod
SomeAsync
YourCode.SomeWork

This tends to make exception stack traces a lot more useful, to say the least. You can see where YourCode.SomeWork was called in the context of your application, rather than "the physical way it was run".

An example of how this works is in the reference source (non-contractual, of course).



Related Topics



Leave a reply



Submit