我有这样的场景,我有一个的WebAPI和端点触发时,做了很多的工作(约2-5min)的。 这是一个POST端点的副作用,我想限制的执行,这样,如果2个请求发送到该端点(不应该发生,但有备无患),其中一人将不得不以避免种族等待条件。
我第一次尝试使用这样的控制器内一个简单的静态锁:
lock (_lockObj)
{
var results = await _service.LongRunningWithSideEffects();
return Ok(results);
}
这当然是不可能的,因为中await
里面lock
的语句。
我考虑的另一个解决方案是使用一个SemaphoreSlim
实现这样的:
await semaphore.WaitAsync();
try
{
var results = await _service.LongRunningWithSideEffects();
return Ok(results);
}
finally
{
semaphore.Release();
}
然而,根据MSDN:
该SemaphoreSlim类表示可用于当等待时间预计是很短的一个单一的过程中等待一个轻量级的,快速的旗语。
因为在这种情况下的等待时间甚至可以达到5分钟,我应该怎么用并发控制?
EDIT(响应于plog17):
我也明白,通过这个任务到服务可能是最佳的方式,但是,我不一定要排队的请求完成后仍然运行在后台的东西。 该请求涉及的其他请求,并需要一些时间整合,但我仍希望用户等待这个请求完成并得到回应不管。 这个请求预计将每天只有一次在特定时间由cron作业发射。 然而,也有由开发人员手动启动它的一个选项(主要是在出错的时候与工作),我想确保API不会遇到并发问题如果开发商如不小心双击发送请求等等
如果只有一个之类的请求可以在给定时间进行处理,为什么不执行队列?
有了这样的设计,不再需要锁定也不等待处理的长期运行的请求。
流程可以是:
- 客户端POST / RessourcesToProcess,应该很快收到202-接受
HttpController只需排队任务进行(并返回202接受的)
其他服务(Windows服务?)出队接下来的任务进行
- 继续任务
- 更新资源状态
在这个过程中,客户端应该能够轻松获得以前的请求的状态:
- 如果任务未找到:404-NOTFOUND。 的ressource找不到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长期运行的任务
- https://blogs.msdn.microsoft.com/webdev/2014/06/04/queuebackgroundworkitem-to-reliably-schedule-and-run-background-processes-in-asp-net/