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()
.
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.
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.
Still confused on ConfigureAwait(false) used with GetAwaiter and GetResult in C#. Getting a deadlock or method not returning
I thought (especially from the first article above) that if I use ConfigureAwait(false) I could call an async method without a deadlock.
There's an important note in that article:
Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).
So, is ConfigureAwait(false)
used for every await
in the transitive closure? This means:
- Does
CloseLoadDoor
useConfigureAwait(false)
for everyawait
? We can see from the code posted that it does. - Does
_model.CloseLoadDoor
useConfigureAwait(false)
for everyawait
? That we cannot see. - Does every method called by
_model.CloseLoadDoor
useConfigureAwait(false)
for everyawait
? - Does every method called by every method called by
_model.CloseLoadDoor
useConfigureAwait(false)
for everyawait
? - etc.
This is a severe maintenance burden at least. I suspect that somewhere down the call stack, there's a missing ConfigureAwait(false)
.
As that note concludes:
As the title of this post points out, the better solution is “Don’t block on async code”.
In other words, the whole point of that article is "Don't Block on Async Code". It's not saying "Block on Async Code with This One Neat Trick".
If you do want to have an API that supports both synchronous and asynchronous callers, I recommend using the bool argument hack in my article on brownfield async.
On a side note, in the code CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult()
, the ConfigureAwait
doesn't do anything. It's "configure await", not "configure task". Since there's no await
there, the ConfigureAwait(false)
has no effect.
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).
Does GetAwaiter() throw a single exception?
Yeah, you can and should use async await
:
(var success, var response) = await HTTPHelper.SendRequest<SomeJsonResponse>( ... )
Then there is no need to wrap it into the Task. I assume .SendRequest<SomeJsonResponse>()
returns the Task
.
FYI: .Wait()
, .Result
or GetAwaiter().GetResult()
are all thread blocking executions
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.
Related Topics
Append Lines to a File Using a Streamwriter
How to Loop Through All Enum Values in C#
Cross-Platform File Name Handling in .Net Core
Check Whether an Array Is a Subset of Another
Use a Variable from Another Method in C#
How to Find the Number of Cpu Cores Via .Net/C#
Easier Way to Debug a Windows Service
Use Linq to Get Items in One List≪≫, That Are Not in Another List≪≫
How to Truncate Milliseconds Off of a .Net Datetime
Cast Generic≪Derived≫ to Generic≪Base≫
What Is the 'Dynamic' Type in C# 4.0 Used For
How to Monitor SQL Server Table Changes by Using C#