【问题标题】:Async Await Performance in Web ApplicationsWeb 应用程序中的异步等待性能
【发布时间】:2017-12-07 18:55:45
【问题描述】:

到目前为止,我想我已经掌握了异步等待如何使您的应用程序更具响应性的概念,但我坚持两点:

图层注意事项 异步等待是否必须从存储库层一直到 MVC 或 WCF 层才能获得性能优势,或者我可以只对需要很长时间的存储库方法执行异步操作吗?

“等待”用法如果我只能在存储库级别工作,那么有一部分我不明白。使用这种(低层)方法,线程是否能够在等待 io 绑定代码完成时为传入的客户端请求提供服务?

在我的脑海中,我整理了一个示例控制台应用程序,当长时间运行的任务仍在继续时,另一个用户可以向我的 Web 应用程序发出请求。使用我的小库(以简化集成和异常处理),他们的请求会由挂起的线程提供服务,还是voidTask.Wait(); 导致线程阻塞?

public class AsyncTest
{
    public void RunTest()
    {
        try
        {
            using (var task = new RunTask(LongRunningTask))
            {
                Console.WriteLine("Echo two things:");

                for (int i = 0; i < 2; i++)
                {
                    var input = Console.ReadLine();
                    Console.WriteLine(string.Format("Echoing \"{0}\"", input));
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error, hm, what happened??");
            Console.WriteLine();
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
        }
    }

    public void LongRunningTask()
    {
        var totals = 0;

        for (int i = 0; i < 30; i++)
        {
            Thread.Sleep(1000);
            totals += 5;

            if (i == 25)
                throw new ArgumentException("I can't handle this! Errorr!!@!@");

            Console.WriteLine(string.Format("LongRunningTask Step {0}...", (i + 1)));
        }
    }
}

public class RunTask : IDisposable
{
    public delegate void IOBoundOperations();
    private Task voidTask;

    public RunTask(IOBoundOperations task)
    {
        voidTask = Execute(task);
    }

    async public Task Execute(IOBoundOperations task)
    {
        await Task.Run(() =>
        {
            task();
        });
    }

    public void Dispose()
    {
        try
        {
            voidTask.Wait();
        }
        catch
        {
            throw new AsyncException("Failed to run task asynchronously: " + 
                voidTask.Exception.InnerException.Message, voidTask.Exception.InnerException);
        }
    }
}

我等待,因为我需要它来执行将数据返回给它可能依赖的调用代码的任务。我也这样做了,因为我注意到在线程也执行完我的异步内容之前,该过程不会完成。

我可以找到很多关于 async/await 及其好处的信息,但在这一特定方面却一无所获。

【问题讨论】:

  • 您发布的代码没有做任何真正的异步工作。它只是安排一个调用同步委托的长时间运行的任务。另请注意,在您的示例中,由于您的RunTask 类的Dispose 方法中的voidTask.Wait 使用了两次“回声”,UI 线程就会阻塞。我看不出该代码与您的问题有何关联。
  • 是的,我试图让它异步运行,但是当应用程序运行时线程不会被强制结束。我的想法是,一个请求进来,我开始一个长时间运行的任务并继续。我不希望仅仅因为请求已完成服务而结束长时间运行的任务。这就是为什么我打电话给wait。我与 Richter 一起观看了一段视频,他解释说没有必要保留 Task,但在控制台应用程序中,线程一旦完成就会停止执行。
  • 我指的链接:channel9.msdn.com/Shows/AppFabric-tv/…(我知道的情况略有不同,这就是我首先在上面的代码中确认的原因。

标签: c# multithreading asynchronous


【解决方案1】:

正如其他人所说,您实际上并没有通过您的示例掌握异步 IO 的真正力量。

当人们意识到异步“一路”从存储库一直到您的控制器时,他们通常会“害怕”。正如我常说的,不要抗拒它,让它在你的代码库中自然增长,因为你开始实现它。我建议不要使用同步而不是异步做任何捷径,反之亦然,付出额外的努力(如果需要)分别公开两个 API。

但是,正如@usr 所说,并不总是需要实际执行异步 IO,即使它是可能的。当然,每个人都想赶上async-await 的潮流,因为这是一件很酷的事情,但请先问问自己,我是否会遇到很多并发请求,而实际上我会从异步 IO 中受益?不要忘记尽管async-await 的开销很小,但它确实有一些开销。

最后,一个小小的个人故事。最近我开始研究一个 ASP.NET Web API 项目,该项目正在访问 SQL 数据库并进行一些繁重的计算。挑战是让计算速度更快,我想知道使数据库调用异步是否会有所帮助(我有一种直觉认为它不会,但我必须尝试)。在添加异步 API 并在整个方法中冒泡之后(这本身就是一项不小的努力),整体性能实际上最终降级(正如我所假设的那样)。因此,请确保您出于正确的原因利用异步 IO。

【讨论】:

    【解决方案2】:

    要从 async/await 范式中获得任何好处,您需要将它一直带到顶部,即您的 MVC/Web API 操作(通过将操作标记为 async 并返回Task 之类的)或在您的 Web 表单方法中(通常通过将它们标记为 async void,并在页面本身上设置 Async 属性)。

    除此之外,您只有两个选择:

    • 调用Task.Wait(),它确实会阻塞,所以你不会得到任何异步行为。
    • 不以任何方式加入该线程,让它飘走,这可能并不理想(即使对于不返回任何内容的日志记录,IIS 线程池系统也不喜欢浮动线程,所以避免在此处使用它)。

    话虽如此,您提到如果您这样做,另一个用户可以调用您的 Web 服务器,这是真的,但重要的是要注意 ASP Web 应用程序始终是“异步”的他们有许多可用的线程。实现async/await 原则上是一个好主意,它有助于提高分配线程的效率,并可能增加可用并发连接的数量,但不需要允许两个并发请求一下子实现。线程池将自行处理。


    如果您担心遍历整个代码库并与他们的 ...Async() 对应项交换同步调用,请记住,您不一定必须一次完成所有事情。如果可以,那很好,因为异步调用具有前面列出的一些优点,但请记住,您可以将 return 方法设置为 return Task.FromResult(object) 以维护异步合同。

    显然这不会异步运行,但是当您迁移现有代码或编写库时请牢记这一点,因为时间只会让越来越多的东西实现该模式,您可以设计来欢迎它现在,而不是希望你以后这样做。

    【讨论】:

    • 感谢您的第二点,我们认为我们有一些任务从用户的角度来看是不必要的,但仍然需要完成。我知道 IIS 将为我管理请求,我只是不确定wait 是否会阻止或释放该线程以供重用。我想我的措辞可以更好。
    【解决方案3】:

    您似乎并没有完全理解为什么异步 IO 是有益的。它解除阻塞线程,从而节省堆栈内存并避免线程池容量过载。

    Wait 显然阻塞了一个线程,因为该函数在目标任务完成之前不会返回。在调用线程资源被消耗。

    您需要使整个调用链异步。如果您只使较低层异步,那么您必须在较高层等待并消耗与您节省的相同数量的线程。

    请注意,大多数网络应用程序无法通过使用异步 IO 来增加吞吐量或降低延迟。您必须了解什么异步 IO 有利于进行良好的调用。不要为炒作而堕落。请参阅Should we switch to use async I/O by default?Why does the EF 6 tutorial use asychronous calls?

    【讨论】:

    • 澄清一下,在我们的应用程序中,我们在负载测试中为大约 3,500 个会话提供了 30 分钟的服务(类似于以前的大版本)。我们的问题点需要一分钟或更长时间才能完成,因为基础查询可能非常复杂。这些已经转换为 sproc,那么运行这些异步有帮助吗?在我在上面的问题(在 cmets 中)链接的视频中,Richter 暗示驱动程序会在等待时释放线程。我想这只是在完整调用是异步的情况下?
    • 1 分钟长的调用是异步的好例子。另一方面,数据库可能无法同时处理多个(少数=服务器上的核心或磁盘数)。所以无论如何你不能有很多并发请求,并且 async 没有一点帮助。
    【解决方案4】:

    使用 async/await 可以非常方便地处理所有与 I/O 相关的操作。一个例子 动作是 SaveAnalytics() 需要 100 毫秒。如果这是同步完成的,那么线程会被阻塞 100 毫秒。如果这是异步完成的,则在 SaveAnalytics() 的所有数据保存到文件/数据库/其他文件之前立即释放线程。当此保存完成时,另一个线程用于将流返回给客户端。

    那么这有什么好处呢?线程很贵!如果在假设云中需要一个高度可扩展的应用程序,您可以通过使所有与 I/O 相关的调用异步来赢得很多。

    如果您的 Web 应用程序负载过重,您将不会收到“503”消息,这意味着“服务器忙”,这也意味着线程池中没有可用线程分发给客户端(它们都可以是忙于同步等待)。

    用户可能不会注意到这一点,因为他们按下“保存”按钮并看到“分析”已保存的消息。在水下,这一切都是异步发生的。

    总而言之,这一切都与您的应用程序的可扩展性有关。如果您的应用程序没有大量使用,那么将所有方法都设为异步是不值得的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-15
      • 1970-01-01
      • 2011-01-11
      • 1970-01-01
      • 2016-01-30
      • 1970-01-01
      • 2015-04-09
      • 1970-01-01
      相关资源
      最近更新 更多