【问题标题】:What is the best practice for asynchronous programming in ASP.NET MVC?ASP.NET MVC 中异步编程的最佳实践是什么?
【发布时间】:2015-03-01 10:11:38
【问题描述】:

根据this 文章,ASP.NET 要求在控制器中使用相同的SynchronizationContext 进行异步操作,否则会阻塞正在运行的线程。作为结论,作者提到我们应该使用这两种方法作为防止线程死锁的最佳实践:

  1. 在您的“库”异步方法中,尽可能使用 ConfigureAwait(false)。
  2. 不要阻塞任务;一直使用异步。

    注意:最好同时应用这两种最佳做法。任何一个都会阻止 死锁,但必须同时应用这两种方法以实现最佳性能和响应能力。

但是是什么目的导致微软使用相同的SynchronizationContext?可能正在使用 ConfigureAwait(false),它禁用相同的同步上下文限制,可能会导致例如不可预测的行为或性能问题。因此,尽可能使用ConfigureAwait(false) 真的是一种好习惯吗?

【问题讨论】:

    标签: c# .net asp.net-mvc async-await synchronizationcontext


    【解决方案1】:

    ASP.NET 要求使用相同的 SynchronizationContext 控制器中的异步操作,否则它会阻塞 正在运行的线程。

    我不太确定那句话是什么意思。 ASP.NET 不需要“相同”的同步上下文。为了让你进入你的HttpContext,你需要SynchronizationContext

    但是是什么目的导致微软使用相同的SynchronizationContext

    我建议您阅读 It's All About SynchronizationContext 以了解有关同步上下文的更大图景。基本上,它是一种允许您在特定线程上发布延续的机制。例如,它可用于将工作编组回 UI 应用程序内的 UI 消息循环线程。

    可能正在使用 ConfigureAwait(false),它会禁用相同的 同步上下文限制,可能导致例如不可预测的行为或 性能问题

    相反。将工作编组回同步上下文确实有(非常少的)开销,因为需要将请求(使用抽象 SynchronizationContext.Post)发布回所需的线程和上下文。通过使用ConfigureAwait(false),您可以节省该时间,并且只需在分配的线程上继续执行。

    因此,使用 ConfigureAwait(false) 真的是一个好习惯吗? 哪里有可能?

    这样做的主要原因是避免死锁。当有人调用Task.ResultTask.Wait 而不是使用await 进行异步等待时,您会遇到经典的死锁场景,同步上下文试图将延续发布回线程,但它当前被阻止,因为ResultWait 正在阻止呼叫。另一个原因是您获得的性能开销较小。

    编辑:

    微软为什么不把这个方法封装在Task类里面呢? 看起来 ConfigureAwait(false) 只有优点。是 有什么缺点吗?

    让我们想象一下以下场景:您执行一个返回Task 并使用await 等待的方法,然后立即更新一些UI 元素。现在,什么对你来说更自然?您希望隐式返回到之前的相同“环境”(UI 线程),还是希望您必须明确指定要返回到相同的环境?

    我认为前者对用户来说“感觉”更自然。

    例子:

    public Task FetchAndReturnAsync(string url)
    {
        var httpClient = new HttpClient();
        return httpClient.GetAsync(url);
    }
    

    你这样称呼它:

    var awesomeUiResult = await FetchAndReturnAsync("http://www.google.com");
    textBox.Text = awesomeUiResult;
    

    你认为应该发生什么?是在等待之后更新文本框更自然,还是因为您现在不在原始上下文中而失败?

    【讨论】:

    • 感谢您详尽的回答。你说ConfigureAwait(false):1.节省时间2.避免死锁。而微软为什么不将这个方法封装在Task 类中呢?看起来ConfigureAwait(false) 只有优点。有什么缺点吗?
    • 缺点是您不返回SynchronizationContext,这会让最终用户感到惊讶。我会编辑我的答案来解释。
    • 我读了你的编辑一百遍,但不明白你的意思:execute a method which returns a Task这个方法在哪里被调用?它是一个 UI(操作方法)线程吗?
    • @dyatchenko 我添加了一个示例。希望这能解释我的想法。
    • 是的,你是对的,我的意思是一样的。非常感谢你!我想我的问题得到了答案。
    【解决方案2】:

    ASP.NET 要求在控制器中对异步操作使用相同的 SynchronizationContext,否则会阻塞正在运行的线程。

    有点,但不完全是。 ASP.NET 为每个传入请求创建一个SynchronizationContext,并且此上下文根本不绑定到特定线程。但是,一次只有一个线程可以进入上下文,所以如果第二个线程尝试进入上下文,而另一个线程已经在其中,那么它将阻塞该线程。

    什么目的导致微软使用相同的 SynchronizationContext?

    用于 ASP.NET 的 SynchronizationContext 管理每个请求的数据,例如 HttpContext.Current、文化和用户身份。

    尽可能使用 ConfigureAwait(false) 真的很好吗?

    一般来说,是的,尤其是对于通用库。在 ASP.NET 上,ConfigureAwait(false) 不会给您带来太多收益 - 在某些情况下,性能会略有提升。它可以用于避免我在文章中提到的死锁,但最好(尤其是在 ASP.NET 上)一直使用async

    【讨论】:

    • 好的。得到你。非常感谢斯蒂芬!但是为什么不能在控制器的构造函数中使用异步操作呢?我在另一个问题中问过这个问题:stackoverflow.com/questions/28805796
    • @dyatchenko:在内部,DownloadStringTaskAsync 正在以与async void 非常相似的方式启动异步操作。
    • 但是为什么我们可以在action方法中使用DownloadTaskAsync没有任何问题呢?
    • @dyatchenko:因为操作方法(异步)等待它完成。构造函数没有。 ASP.NET 看到异步操作还没有完成,所以它抛出了那个异常。
    猜你喜欢
    • 2011-08-12
    • 2014-10-22
    • 2018-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-05
    • 2012-08-03
    • 1970-01-01
    相关资源
    最近更新 更多