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 incompleteTask
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 await
s 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
Fill Panel with Gradient in Three Colors
How to Concatenate Two System.Io.Stream Instances into One
Does Reactive Extensions Support Rolling Buffers
How to Implement Full Row Selecting in Gridview Without Select Button
Avoiding Null Reference Exceptions
Wpf Combobox: Different Template in Textbox and Drop-Downlist
Trace Listener to Write to a Text Box (Wpf Application)
How to Remove a Single Attribute (E.G. Readonly) from a File
Asynchronous File Download with Progress Bar
C# Waiting for Multiple Threads to Finish
Encrypting/Decrypting Large Files (.Net)
How to Put Conditional Required Attribute into Class Property to Work with Web API
Simulating Cross Context Joins--Linq/C#
Monitor a Process's Network Usage