如何在 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 不会 运行 进入并发问题,如果开发商例如不小心重复发送请求等
如果在给定时间只能处理一个此类请求,为什么不实施队列?
通过这样的设计,在处理长运行请求时不再需要锁定和等待。
流量可能是:
- 客户端 POST /RessourcesToProcess,应该很快收到 202-Accepted
HttpController 只是将任务排队以继续(并且 return 202-接受)
其他服务(windows 服务?)出列下一个任务以继续
- 继续任务
- 更新资源状态
在此过程中,客户端应该能够轻松获取之前发出的请求的状态:
- 如果找不到任务: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 中处理长 运行 进程。事实上,它可能不是最好的设计选择。如果您仍然想这样做,您可能需要阅读:
我有这样一个场景,我有一个 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 不会 运行 进入并发问题,如果开发商例如不小心重复发送请求等
如果在给定时间只能处理一个此类请求,为什么不实施队列?
通过这样的设计,在处理长运行请求时不再需要锁定和等待。
流量可能是:
- 客户端 POST /RessourcesToProcess,应该很快收到 202-Accepted
HttpController 只是将任务排队以继续(并且 return 202-接受)
其他服务(windows 服务?)出列下一个任务以继续
- 继续任务
- 更新资源状态
在此过程中,客户端应该能够轻松获取之前发出的请求的状态:
- 如果找不到任务: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 中处理长 运行 进程。事实上,它可能不是最好的设计选择。如果您仍然想这样做,您可能需要阅读: