【问题标题】:Is it necessary for a controller action that only returns a view to be asynchronous?仅返回视图的控制器操作是否需要异步?
【发布时间】:2019-03-03 10:46:07
【问题描述】:

采取这个基本行动:

[HttpGet]
public IActionResult Index()
{
    return View();
}

修改它以使用 async/await:

[HttpGet]
public async Task<IActionResult> Index()
{
    return await Task.Run(() => View());
}

我很困惑这是否会改进我的代码。根据我的理解,await 关键字会释放调用线程,以便它可以在其他地方使用,这样可以更好地利用可用线程。

但除了返回视图的这一件事之外,我实际上并没有做任何其他事情。使用 async 关键字实际上在编译代码中引入了一个状态机,这增加了复杂性。

让这个动作异步值得吗?有没有更好的方法来修改它以使其异步?

【问题讨论】:

  • 您还没有真正将控制器修改为异步的。无论如何,所有请求都由单独的线程处理。 async/await 旨在避免在执行 IO 工作时阻塞这些线程。如果您使用await Task.Run(),您只需使用一个新任务/线程来完成原始线程可以轻松完成的工作
  • await Task.Run() 几乎总是错误的。 - Task.Run() - “TPL,请在我忙于做其他事情时找到一个线程来运行此代码”。 await - “我在 this 线程上没有有用的工作要做 - 去看看有没有其他东西可以利用它”
  • @Damien_The_Unbeliever 只有在 ASP 上下文中才真正如此。确实如此,但您的陈述暗示它适用于其他情况,但它非常不正确。
  • @Servy - 在 asp 上下文中确实如此。在控制台上下文中确实如此。如果它正在运行其他经常await本身并且不会占用CPU的代码,那么在具有“祝福线程”(例如UI线程)的上下文中可能是正确的。如果您试图从 UI 线程中推出受 CPU 限制的工作,则不是这样。但我不经常看到它在那种情况下使用。
  • 这是一个人为的问题。当您需要数据来填充模型时,它会发生变化。当你有一些元素在做异步 I/O 时,使 Action 异步是有利可图的。

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


【解决方案1】:

简短的回答是否定的。因为在View() 中没有发生任何事情async,所以在执行过程中没有任何地方可以让编译器释放线程以允许在同一线程上进行其他工作。

我会说,使这个特定方法异步可能(甚至稍微)性能更差,因为一​​旦您使用 async 关键字,编译器将需要修改您的代码,使执行的内容更加复杂。

如果您有很多其他实际上在利用async 的操作,我可以看到将其设为async 的唯一好处是保持一致性。如果您真的希望拥有Task&lt;T&gt;签名以保持一致性,您可以考虑:

public Task<IActionResult> Index()
{
    return Task.FromResult(View());
}

(注意没有async 涉及关键字,也没有Task.Run 启动新任务来运行)

【讨论】:

    【解决方案2】:

    在我看来,您应该重新访问 Microsoft 的 TAP 文档,并更好地了解如何使 Task 为您工作。

    在你的例子中:

    public Task<IActionResult> Index()
    {
        return Task.FromResult(View());
    }
    

    它本质上只是返回一些 HTML、Javascript 和 CSS,所以它不是毫无意义的,这意味着当你将 Task 添加到普通函数时,会有开销会变得很昂贵。

    如果你有这样的事情:

    public Task<IActionResult> Index()
    {
        var users  = DbContext.GetUsersAsync();
        var groups = DbContext.GetGroupsAsync();
    
         await Task.WhenAll(users,groups);
    
        var m = new Model(){
         Users = users.Result,
         Groups = groups.Result
        }
    
        return View(m);
    }
    

    在这种情况下,Task 是有意义的,您实际上是在同时执行两个不同的 I/O 功能,然后等待它们全部完成后再继续。您正在花费单独处理每个调用所需的时间,并希望将其减少一半(以阻塞潜在的 CPU 线程为代价,以供另一个“网络”用户访问同一文档。)

     public Task<IActionResult> Index()
        {
            var users  = await DbContext.GetUsersAsync();
            var groups = await DbContext.GetGroupsAsync();
    
            var m = new Model(){
             Users = users,
             Groups = groups
            }
    
            return View(m);
        }
    

    在我的第三个示例中,即使它使用 Async / Await,它仍然会一个接一个地运行所有内容,因此浪费了宝贵的线程。

    我唯一可以推荐的是,Task 不能很好地适应庞大的用户群,因此在开发您的 Web 解决方案时,请记住用户未来的潜在增长并依赖于 Task-基于函数。

    https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap

    【讨论】:

    • 只是想问一下为什么Task不能很好地适应庞大的用户群?另外,你是什么意思庞大的用户群?
    • TAP 是一个很好的范例,可以让一些函数变得异常快,但是人们有滥用它并将与 Task 相关的所有 I/O 包装起来的习惯,就像一些开发人员将每四行代码包装成一次尝试,没有意识到这一切都是有代价的。此外,当一个函数正在执行一个任务并且它正在消耗多个线程时,如果 CPU 被最大化,它们将开始阻止新用户,而不是完成队列中的任务,所以当你有一百万用户和弱服务器,它可以很快将其耗尽。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    • 2014-11-26
    • 2013-01-17
    • 2019-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多