Reputation: 58931
In an ASP.NET 4.5 application, which of these is better for calling an async method from a sync method?
var result = Task.Run(() => SomethingAsync()).GetAwaiter().GetResult();
// or
var temp = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
return SomethingAsync().GetAwaiter().GetResult();
}
finally
{
SynchronizationContext.SetSynchronizationContext(temp);
}
Note: Yes, I know I should be using async/await
all the way down, but I'm asking about the very bottom, and outside of ASP.NET Core the filters and razor views are not async, so if I want to call an async method from a filter or a razor view, I need to sync it some way. Just using SomethingAsync().GetAwaiter().GetResult()
leads to a deadlock, because of the SynchronizationContext
, so I need a way to run this code without a SynchronizationContext
.
EDIT Here is the simple helper class that wraps this up cleanly:
public static class Async
{
public static T Run<T>(Func<Task<T>> func)
{
var context = SynchronizationContext.Current;
if (context == null)
{
return func().GetAwaiter().GetResult();
}
SynchronizationContext.SetSynchronizationContext(null);
try
{
return func().GetAwaiter().GetResult();
}
finally
{
SynchronizationContext.SetSynchronizationContext(context);
}
}
public static void Run(Func<Task> func)
{
var context = SynchronizationContext.Current;
if (context == null)
{
func().GetAwaiter().GetResult();
return;
}
SynchronizationContext.SetSynchronizationContext(null);
try
{
func().GetAwaiter().GetResult();
}
finally
{
SynchronizationContext.SetSynchronizationContext(context);
}
}
}
// Example
var result = Async.Run(() => GetSomethingAsync("blabla"));
Upvotes: 5
Views: 1716
Reputation: 5496
Obviously it is not ideal to call async code synchronously, however if you must I would say avoid Task.Run
if you can help it.
Task.Run
has a number of issues over SetSynchronizationContext(null)
:
SynchronizationContext
it makes you block synchronously for the entire Task returning function, rather than just the area that needs it, you are also multiplying your problem each time you use it. You will end up with longer blocking over async code, this is certainly not what you want.Task.Run
now is introducing blocking over async, whereas SetSynchronizationContext(null)
will not cost you anything.Finally, an alternative suggestion is to use something like AsyncPump.Run
(Stephen Toub) when you block on a Task
returning function. It waits in such a way that the queued continuations get run by the blocking thread, this way you don't pay the price of multiple concurrent threads and you get no deadlocks, obviously still not as good as just using async all the way.
Upvotes: 2