如何在 Web Api 操作中锁定长时间的异步调用?

How to lock a long async call in a WebApi action?

我有这样一个场景,我有一个 WebApi 和一个端点,当触发时它会做很多工作(大约 2-5 分钟)。这是一个具有副作用的 POST 端点,我想限制执行,以便如果将 2 个请求发送到此端点(不应该发生,但总比抱歉安全),其中一个将不得不等待为了避免竞争条件。

我首先尝试在控制器内部使用一个简单的静态锁,如下所示:

lock (_lockObj)
{
    var results = await _service.LongRunningWithSideEffects();
    return Ok(results);
}

这当然是不可能的,因为lock语句里面有await

我考虑的另一种解决方案是使用这样的 SemaphoreSlim 实现:

await semaphore.WaitAsync();
try
{
    var results = await _service.LongRunningWithSideEffects();
    return Ok(results);
}
finally 
{
    semaphore.Release();
}

但是,根据 MSDN:

The SemaphoreSlim class represents a lightweight, fast semaphore that can be used for waiting within a single process when wait times are expected to be very short.

由于这种场景下等待时间甚至可能达到5分钟,我应该使用什么来进行并发控制?

编辑(响应 plog17):

我知道将此任务传递给服务可能是最佳方式,但是,我不一定想在后台排队一些在请求完成后仍然 运行s 的东西。 该请求涉及需要一些时间的其他请求和集成,但我仍然希望用户等待此请求完成并无论如何获得响应。 该请求预计每天仅在特定时间由 cron 作业触发一次。但是,还有一个选项可以由开发人员手动触发它(主要是为了防止工作出现问题),我想确保 API 不会 运行 进入并发问题,如果开发商例如不小心重复发送请求等

如果在给定时间只能处理一个此类请求,为什么不实施队列?

通过这样的设计,在处理长运行请求时不再需要锁定和等待。

流量可能是:

  1. 客户端 POST /RessourcesToProcess,应该很快收到 202-Accepted
  2. HttpController 只是将任务排队以继续(并且 return 202-接受)

  3. 其他服务(windows 服务?)出列下一个任务以继续

  4. 继续任务
  5. 更新资源状态

在此过程中,客户端应该能够轻松获取之前发出的请求的状态:

  • 如果找不到任务:404-NotFound。找不到 id 123
  • 的资源
  • 如果任务处理:200-OK。 123 正在处理中。
  • 如果任务完成:200-OK。处理响应。

您的控制器可能看起来像:

public class TaskController
{

    //constructor and private members

    [HttpPost, Route("")]
    public void QueueTask(RequestBody body)
    {
        messageQueue.Add(body);
    }

    [HttpGet, Route("taskId")]
    public void QueueTask(string taskId)
    {
        YourThing thing = tasksRepository.Get(taskId);

        if (thing == null)
        {
            return NotFound("thing does not exist");
        }
        if (thing.IsProcessing)
        {
            return Ok("thing is processing");
        }
        if (!thing.IsProcessing)
        {
            return Ok("thing is not processing yet");
        }
        //here we assume thing had been processed
        return Ok(thing.ResponseContent);
    }
}

此设计建议您不要在 WebApi 中处理长 运行 进程。事实上,它可能不是最好的设计选择。如果您仍然想这样做,您可能需要阅读: