【问题标题】:Web API and Async/Await Benefits on a Single Core Machine单核机器上的 Web API 和 Async/Await 优势
【发布时间】:2014-03-04 16:46:32
【问题描述】:

我在另一个thread 中询问了一个关于 TPL 中 GDI+ 问题(异步/等待)的问题,讨论转向了使用 TPL 是否有任何好处的问题。

所以我想在这里理解这个问题的答案。

场景大致是这样的:

  • Web API 控制器/方法接收图像上传
  • 调整图像大小并将其上传到 azure 的方法针对各种尺寸被多次调用 (
  • 该方法为每个调整大小和上传的图像返回一个 Uri
  • 向 Web API 客户端返回响应

请注意,这可能会在单核机器上运行,因此并行运行所有调整大小不会带来任何好处(例如,缩短请求的总长度)。

但我的印象是,将所有各种调整大小包装到一个方法中并异步运行该方法至少将 Web API 线程暂时返回到池中,以处理另一个请求(同时一个常规线程运行调整大小的任务),这是一件好事。代码如下所示:

public Dictionary<ProfilePhotoSize, Uri> ProcessImages(Stream photoStream)
{
    var imgUris = new Dictionary<ProfilePhotoSize, Uri>()
    {
        ProfilePhotoSize.FiveHundredFixedWidth, ResizeAndUpload(ProfilePhotoSize.FiveHundredFixedWidth, photoStream)},
        ProfilePhotoSize.Square220, ResizeAndUpload(ProfilePhotoSize.Square220, photoStream)},
        ProfilePhotoSize.Square140, ResizeAndUpload(ProfilePhotoSize.Square140, photoStream)},
        ProfilePhotoSize.Square80, ResizeAndUpload(ProfilePhotoSize.Square80, photoStream)},
        ProfilePhotoSize.Square50, ResizeAndUpload(ProfilePhotoSize.Square50, photoStream)}
    };

    return imgUris;
}

还有……

var photoUris = await Task.Run(() => _photoService.ProcessImages(photoStream);

所以问题是 - 我是否偏离了基地?也许这个理论是合理的,但它实现得并不完全正确(也许我需要使用 ConfigureAwait)?

这里的现实是什么?

【问题讨论】:

  • 看看这里tugberkugurlu.com/archive/… 另外,为什么不使用 Azure WebJobs 来调整图像大小并上传到 Azure 存储?
  • @MatijaGrcic 阅读您的链接,似乎支持我的理解 - 是吗?至于 Azure WebJobs,我们可能会离开 Azure,但如果我们不这样做,我会看看它......

标签: c# azure task-parallel-library asp.net-web-api


【解决方案1】:

但我的印象是,将所有各种调整大小包装到一个方法中并异步运行该方法至少会暂时将 Web API 线程返回到池中,以处理另一个请求(而常规线程运行调整大小任务),这是一件好事。

不,不是真的。如果您有真正的异步工作要做,那么是的,您将从使用asyncawait 中获得可扩展性优势。但是,你的工作是 CPU 密集型的,所以这样的代码:

var photoUris = await Task.Run(() => _photoService.ProcessImages(photoStream);

刚刚结束使用另一个线程池线程(Task.Run),允许请求线程返回线程池。所以它实际上增加了开销,并没有给你带来任何可扩展性的好处。

在 ASP.NET 上,如果您有 CPU 密集型工作要做,只需直接调用该方法。不要用Task.Run 包裹它。

【讨论】:

  • 暂时搁置额外的 Azure 上传片段(不受 CPU 限制)——我知道处理 web api 请求的线程是“特殊的”,我们可以在很长一段时间内释放它们——使用上述代码运行任务(因为它在非“特殊”线程上执行),这很好。我猜我理解的某些部分是不正确的,但我不清楚是哪一部分。然后,如果我们重新引入 Azure 上传片段?
  • 它们唯一的“特别”之处在于它们有一个请求上下文。它们仍然是线程池线程,来自Task.Run 使用的同一个线程池,通过“窃取”这些线程并稍后“注入”它们,您可以摆脱线程池的启发式。所以在 ASP.NET 上(几乎)没有 Task.Run 的用例。 Azure 上传将受 I/O 限制,因此非常适合 async。由于您有混合工作(CPU-bound,然后是 I/O-bound),同步执行 CPU-bound 工作,然后异步执行 I/O-bound 工作。不要使用Task.Run
  • 太棒了!我们要找到我误解的根源了!我们不能在非 ThreadPool 线程上运行这个 CPU 绑定的代码,并且没有简单的方法来改变这种行为(例如,我不想卸载到服务)?
  • 好吧,您可以技术上创建一个新的非线程池线程并执行代码。但同样,这并不能真正为你买任何东西。 ASP.NET 线程池启发式假设 ASP.NET 控制所有线程。将受 CPU 限制的工作卸载到 Azure Worker 角色或 Azure Job 并不是一个坏主意。如果这项工作很重要,我当然会考虑。但在这种情况下,您需要弄清楚在工作完成之前要返回给客户的内容,以及通知客户工作完成的某种方式。
【解决方案2】:

如果您的 asp.net 应用程序使用线程池中的太多线程(如果您有大量长时间运行的请求,则可能会发生这种情况),您可能会看到它的性能和响应能力有所提高。如果请求队列已满,Web 服务器会拒绝 HTTP 503 状态(服务器太忙)的请求。根据 Microsoft 的说法,在某些情况下异步代码的性能优势可能非常显着:

使用同步方法为高延迟调用提供服务的 Web 应用程序,其中线程池增长到 .NET 4.5 默认最大值 5,与能够使用异步方法处理相同请求的应用程序相比,000 个线程将消耗大约 5 GB 的内存并且只有 50 个线程。当你做异步工作时,你并不总是使用线程。例如,当您发出异步 Web 服务请求时,ASP.NET 不会在异步方法调用和等待之间使用任何线程。使用线程池处理具有高延迟的请求会导致较大的内存占用和服务器硬件的利用率低

然而,CPU 密集型操作并非如此,只有网络密集型或 I/O 密集型

有关更多信息,请查看Using Asynchronous Methods in ASP.NET MVC 4,它也适用于 Web 托管的 Web API 应用程序。

【讨论】:

  • 问题是,他根本没有减少运行线程的数量。
  • 你能说得更具体点吗?你尝试了多少并发请求,线程池的大小是多少?
猜你喜欢
  • 1970-01-01
  • 2015-09-28
  • 1970-01-01
  • 2016-10-24
  • 2017-06-16
  • 2021-11-18
  • 2021-06-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多