【问题标题】:Most efficient way to save a file on disk while executing async work, on a ASP.Net CORE Web Request在 ASP.Net CORE Web 请求上执行异步工作时将文件保存在磁盘上的最有效方法
【发布时间】:2020-12-12 22:42:07
【问题描述】:

根据我的 Web API 的请求,我将图像保存到磁盘并使用外部 API 处理它,这通常需要几秒钟。这是一个高流量的 API,因此我想以最有效的方式设计它。图像采用 Base64“编码”,但这无关紧要。我们可以将其视为平均 150KB 的任意字节数组(因此保存到磁盘的操作应该非常快)

工作流程是(前两个操作显然不需要按任何顺序执行):

  • 将图像保存在磁盘上(异步)
  • 在外部 API 上处理它(异步)
  • 如果前面的两个操作都成功,则返回 Ok

考虑到这一点,我整理了这段(简化的)代码:

public async Task<IActionResult> Post([FromBody] string imageBase64)
{
    // Convert Image
    byte[] imageByteArray = Convert.FromBase64String(imageBase64);

    // Start async Task to Save Image
    Task taskSaveImage = System.IO.File.WriteAllBytesAsync(@"C:\ImagesStorage\image.jpg", imageByteArray);

    // Execute some heavy async processing
    await ProcessOnExternalAPI(imageByteArray);

    // Guarantee that Save Image Task has completed
    await taskSaveImage;

    // Return 200 Ok
    return Ok();
}

在我看来,这段代码是最有效的将图像保存在磁盘上的方法,并且还使用外部 API 处理它,同时不阻塞 ASP.Net CORE 工作线程 .是这样吗,还是有更有效的方法?

另外,在两个任务(因此可能是两个线程)之间共享byte[] imageByteArray 对象有什么问题吗?我相信 .Net CORE 会处理它,但我不会很高兴在生产中发现我错了。

  • Obs 1:我知道接收字节数组的流可能比接收 Base64 编码的字节数组具有更好的性能。我对此无能为力。这就是 API 必须的样子。
  • Obs 2:使用来自System.Net.HttpClient 类的async 方法PostAsync 发出对外部RESTful API(在上面的ProcessOnExternalAPI 方法内)的请求。
  • Obs 3:我最关心的是始终有工作线程来响应请求。这不是我的 API 响应的唯一服务。
  • Obs 4:我还在 SO 上查看了这个问题/答案:Using async/await for multiple tasks 但在那边,我主要关心的是 asp.net 核心工作线程。
  • Obs 5:代码是根据我对 ASP.Net CORE 异步编程的浅薄知识编写的,并且还依赖于 Microsoft 的文档:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/ 也就是说,我很想听听在这种情况下对异步有更多经验的人的意见。

【问题讨论】:

  • 您对“最有效方式”的标准是什么?你用这个 API 方法完成的秒数来衡量它吗?或您使用的资源数量?还是其他方式?
  • 大约在这个时候,您将开始进行基准测试。
  • @deezg 最有效的方式是它并行执行任务的方式,同时保持实际处理 asp.net 核心请求的线程畅通无阻,并可在执行前面提到的任务时处理另一个请求
  • @TheGeneral 那么目前没有明显更好的实现吗?我的担心可能是没有看到这种方法明显有问题,或者我做了一些假设,这实际上对这个实现是不正确的
  • 您在这里只有 2 个简单的调用,您已经在做正确的事情(假设您不想像已经讨论过的那样采取更激烈的行动)。您也可以使用 await Task.WhenAll,尽管执行时间不会有明显差异

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


【解决方案1】:

是这样吗,还是有更有效的方法?

就线程而言,这是正确的:您的代码正在执行两个并发异步操作。

我确实更喜欢使用Task.WhenAll,因为这使代码的意图更加明确(并且还处理了写入磁盘任务可能成为即发即弃的边缘情况,正如 Theodor 在cmets):

public async Task<IActionResult> Post([FromBody] string imageBase64)
{
  // Convert Image
  byte[] imageByteArray = Convert.FromBase64String(imageBase64);

  // Start async Task to Save Image
  Task saveImageTask = System.IO.File.WriteAllBytesAsync(@"C:\ImagesStorage\image.jpg", imageByteArray);

  // Start task to call API
  Task processTask = ProcessOnExternalAPI(imageByteArray);

  // Asynchronously wait for both tasks to complete.
  await Task.WhenAll(saveImageTask, processTask);

  // Return 200 Ok
  return Ok();
}

另外,在两个任务(因此可能是两个线程)之间共享 byte[] imageByteArray 对象有什么问题吗?我相信 .Net CORE 会处理它,但我不会很高兴在生产中发现我错了。

不,没有问题。分享不变的价值观是安全的。

【讨论】:

  • Task.WhenAll 方法确实让代码更清晰。关于“即发即弃”的问题,只有当我忘记为该任务编写 await 后才会发生这种情况(我的示例代码中的行 await taskSaveImage;),对吗?似乎只漏掉了一点:就线程而言,这种方法是否正确地让 Asp.NET Core 工作线程在执行异步操作时可用?
  • "如果我忘记了" - 不;提到的问题是,如果ProcessOnExternalAPI 抛出,那么taskSaveImage 永远不会是awaited;通过使用Task.WhenAll,您可以确保它们都始终是awaited,即使有人抛出。是的,这种方法在await Task.WhenAll 行将线程返回到线程池。
  • 现在我明白了。谢谢你的解释!
猜你喜欢
  • 2016-12-30
  • 1970-01-01
  • 2011-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-07
  • 2014-05-17
  • 1970-01-01
相关资源
最近更新 更多