13

I have an async Task that needs to be called synchronously (yes, unfortunately, it is unavoidable). It seems that there are two ways of implementing this - each seeming to work. So I'm unsure which is the best approach, or if there is a better one.

For example:

var meetings = Task.Run(() => GetTodaysMeetingsAsync()).GetAwaiter().GetResult();

var meetings = GetTodaysMeetingsAsync().GetAwaiter().GetResult();

If someone could explain why one approach is better than another, that would be greatly appreciated. Thanks!

Adam
  • 370
  • 1
  • 5
  • 11
  • They are not equivalent. First one actually has a bug where it leaks the returned `Task`. – Tanveer Badar Nov 24 '19 at 14:13
  • Please could you elaborate? – Adam Nov 24 '19 at 14:25
  • If you are on the .Net Framework `.GetAwaiter().GetResult()` can deadlock. But if your on .Net Core it is fine. – Frank Nielsen Nov 24 '19 at 14:43
  • What is the signature of the `GetTodaysMeetings` method? If it return a `Task`, then it should have the `Async` suffix. Also what is the type is of your application? Windows Forms? ASP.NET? – Theodor Zoulias Nov 24 '19 at 14:57
  • 2
    @FrankNielsen That only holds true for ASP.NET Core which got rid of the `SynchronizationContext`. – Tanveer Badar Nov 24 '19 at 15:08
  • @TanveerBadar as i said, it goes for .Net Core (as Asp.Net Core is part of) – Frank Nielsen Nov 24 '19 at 15:11
  • 2
    @FrankNielsen It only goes for ASP.NET Core. It does not go for the entire .NET Core. – GSerg Nov 24 '19 at 15:12
  • 2
    @FrankNielsen That is entirely incorrect. .NET core is much more than ASP.NET core, the latter is a part of former, don't conflate the two. With WPF and Windows Forms coming to .NET core in 3.0, it is even more problematic that you would advice anyone to not care about deadlocks if they are on .NET core. – Tanveer Badar Nov 24 '19 at 15:12
  • Ok, havent moved to the 3.0 platform yet. So is my answer correct if there is no `SynchronizationContext`/ui-thread like asp.net core, console? – Frank Nielsen Nov 24 '19 at 15:23
  • 1
    It is correct for ASP.NET and console, unless someone decides to install a non-default `SynchronizationContext` in the running program. – Tanveer Badar Nov 24 '19 at 15:31
  • The code is simply ran on a Console App with .Net 3, so I'm assuming the suggestion by @FrankNielsen is to be ignored? – Adam Nov 24 '19 at 15:59
  • .Net 3 is ambiguous. I guess you probably mean .NET Core 3. – Theodor Zoulias Nov 24 '19 at 16:05
  • Well, yes ok then. – Adam Nov 24 '19 at 16:08

1 Answers1

18

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().

Stuart
  • 5,358
  • 19
  • 28
  • 1
    For learning if you are running under a `SynchronizationContext`, you can also query the property [`SynchronizationContext.Current`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext.current). – Theodor Zoulias Nov 24 '19 at 16:28