Task.Run 与 null SynchronizationContext

Mar*_*ius 5 c# asp.net asp.net-mvc asynchronous razor

在 ASP.NET 4.5 应用程序中,哪一个更适合从同步方法调用异步方法?

var result = Task.Run(() => SomethingAsync()).GetAwaiter().GetResult();

// or 

var temp = SynchronizationContext.Current;
try
{
    SynchronizationContext.SetSynchronizationContext(null);
    return SomethingAsync().GetAwaiter().GetResult();
}
finally
{
    SynchronizationContext.SetSynchronizationContext(temp);
}
Run Code Online (Sandbox Code Playgroud)

注意:是的,我知道我应该async/await一直使用,但我问的是最底层,在 ASP.NET Core 之外,过滤器和剃刀视图不是异步的,所以如果我想调用异步方法从过滤器或剃刀视图,我需要以某种方式同步它。SomethingAsync().GetAwaiter().GetResult()由于 ,仅仅使用就会导致死锁SynchronizationContext,所以我需要一种在没有SynchronizationContext.

编辑 这是一个简单的帮助器类,它干净地包装了它:

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"));
Run Code Online (Sandbox Code Playgroud)

Stu*_*art 2

显然,同步调用异步代码并不理想,但是如果您必须这样做,我会说Task.Run如果您可以帮助的话就避免这样做。

Task.Run有很多问题SetSynchronizationContext(null)

  • 目前尚不清楚您为什么要使用它,您想在网络服务器上启动新任务吗?如果没有评论,新开发者很快就会删除。然后繁荣,难以诊断生产问题(死锁)。
  • 当不需要时,它会在新的线程池线程上启动,直到到达第一个等待完成的延续,而不是同步运行。(这是一个非常小的优化)
  • 如果您从多个级别执行此操作以保护您的SynchronizationContext系统,那么您会同步阻塞整个任务返回函数,而不仅仅是需要它的区域,那么每次使用它时您的问题也会成倍增加。您最终会在异步代码上遇到更长的阻塞,这当然不是您想要的。
  • 如果事实证明您认为没有阻塞/死锁,或者随后修复了它,那么Task.Run现在正在引入异步阻塞,而SetSynchronizationContext(null)不会花费您任何费用。

最后,一个替代建议是当您阻止返回函数时使用类似AsyncPump.Run(Stephen Toub)Task的东西。它以这样一种方式等待,即排队的延续由阻塞线程运行,这样您就不必付出多个并发线程的代价,并且不会出现死锁,显然仍然不如一直使用异步好。