将cpu绑定/io绑定的长时间运行代码封装到(async)Task中

本文关键字:绑定 async Task 封装 运行 cpu io 长时间 代码 | 更新日期: 2023-09-27 18:27:02

考虑简单的MVC5控制器:

public class DocumentsController {
    // ctor code is omitted
    [HttpPost, Route("api/documents/request/stamp={stamp}")]
    public ActionResult RequestDocuments(string stamp) {
        var documents = this.DocumentsRequestService.RequestByStamp(stamp);
        return new JsonResult(documents);
    }
}

DocumentsRequestService在内部执行以下操作:

  1. 它向专用MSMQ队列发送请求(让我们称之为M)并且同步M的响应队列等待传入消息:

    using(var requestMessage = new Message()) {
        requestMessage.Body = documentStamp;
        requestMessage.Recoverable = true;
        requestMessage.Label = "request";
        requestMessage.ResponseQueue = this.requestedDocumentsResponseQueue;
        requestMessage.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
        // send request
        this.requestedDocumentsQueue.Send(requestMessage);
        // synchronously wait for response
        var responseMessage = this.requestedDocumentsResponseQueue.Receive();
        if(responseMessage.Label.EndsWith("success")) {
            return new DocumentsRequestResult(
                success: true,
                matches: parseMatchesList(responseMessage)
            );
        }
        return new DocumentsRequestResult(
            success: false,
            matches: Enumerable.Empty<DocumentsRequestMatch>()
        );
    }
    
  2. 该消息的使用者(Windows服务)进行特定的api调用。我所说的"具体"是指我们使用第三方手段来做到这一点。此调用是同步的,并且相当长。当处理结束时,使用者向请求消息的响应队列发送一条响应消息。

  3. 当响应到达M的响应队列时,需要解析并将结果返回给控制器

从最终用户的角度来看,这个任务应该是阻塞的,或者至少看起来应该是阻塞。

据我所知,运行任务会利用并行化。而使用async-await对则会使正在运行的任务异步。如果几个任务并行运行,可能会有所帮助。

在我的情况下,与任务/异步结合是否合理/可能?如果是,那么我从哪里开始?

将cpu绑定/io绑定的长时间运行代码封装到(async)Task中

网络调用的"异步性"对调用者是透明的。对于调用者来说,实现是同步的还是异步的并不重要。换句话说,从客户端的角度来看,它总是异步的。

例如,HTTP客户端根本不在乎RequestDocuments是同步的还是异步的;无论哪种方式,HTTP客户端都将在一段时间后发送请求并接收响应(即异步)。

类似地,HTTPWeb服务器并不关心Win32服务是同步实现的还是异步实现的。它只知道它将一条消息放入队列,一段时间后(即异步)它从队列中获得一条响应消息。

据我所知,运行Task是利用并行化的。而使用异步等待对则使正在运行的任务异步。

有点。Task既可以用于异步代码,也可以用于并行代码,这一事实引起了很多混乱。然而,像Parallel和PLINQ这样的任务并行库结构在并行(非异步)世界中是稳固的。

如果几个任务并行运行,可能会有所帮助。

我认为"并发"在这里是合适的术语。

首先,请注意,ASP.NET免费为您提供了大量的并发性。如果您想使每个请求在内部并发,那么您可以通过Task.WhenAll很容易地做到这一点。例如,您可以将DocumentsRequestService调用更改为异步调用(假设您的消息队列API支持异步调用):

using(var requestMessage = new Message()) {
  ...
  // send request
  await this.requestedDocumentsQueue.SendAsync(requestMessage);
  // asynchronously wait for response
  var responseMessage = await this.requestedDocumentsResponseQueue.ReceiveAsync();
  ...
}

然后你可以从一个控制器动作中同时调用它多次,例如:

public async Task<ActionResult> RequestDocuments(string stamp1, string stamp2) {
  var task1 = this.DocumentsRequestService.RequestByStampAsync(stamp1);
  var task2 = this.DocumentsRequestService.RequestByStampAsync(stamp2);
  var documents = await Task.WhenAll(task1, task2);
  return new JsonResult(documents);
}