Marius
Marius

Reputation: 58931

Task.Run vs null SynchronizationContext

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

Answers (1)

Stuart
Stuart

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):

  • It is not clear why you are using it, you want to start a new task on a web server? This will be deleted by new developers fast if there are no comments. And then boom, difficult to diagnose production issues (deadlocks).
  • It starts on a new thread pool thread when it doesn't need to until it reaches its first await completed continuation, vs running synchronously. (this is a very minor optimization)
  • If you are doing this from multiple levels to protect your 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.
  • If it turns out that there was no blocking / deadlock where you thought there was, or it is subsequently fixed, 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

Related Questions