如何为请求排队工作,只为同一个工作执行一次

时间:2017-02-23 11:59:58

标签: c# multithreading asp.net-web-api

我正在开发一个API,我的端点接受一个I​​D,然后执行繁重的操作来编写结果(生成PDF)。

我可能会在短时间内多次获得相同资源的相同请求,因此我想要某种跟踪ID的工作队列,第一个请求会启动实际工作并且以下请求将(首先检查它是否已经在管道中)然后等待工作完成并返回相同的结果。

首先我想也许ConcurrentDictionary会有用,但我不知道如何处理等待工作完成部分。我也看了ObservableCollection,但我不确定它的安全性(没有花太多精力手动安全)。

2 个答案:

答案 0 :(得分:2)

如果您希望能够扩展它,我会建议使用消息队列的替代架构(例如RabbitMQ)。您的API只会将消息发布到队列中,然后您可以使用Windows服务来使用消息并处理它们。这两个应用程序当然可以共享一个公共数据存储,以便同步信息。

答案 1 :(得分:0)

我可能会使用一个普通的字典,其中有一个锁定和Task内部,当计算出工作项的结果时,它就会完成。

这意味着将每部分工作表示为Task<WorkResult>

如果有新请求,您可以执行以下操作:

lock (currentWork)
{
  Task<WorkResult> workItem = null;
  if (currentWork.TryGetResult(workId, out workItem))
  {
    // Work is already in progress. Reuse that work item
    return workItem; // The caller can await that
  }

  // Create a new workItem
  workItem = FunctionThatStartsWork(); // Could also be a Task.Run(...) thing
  // Store it in the map
  currentWork[workId] = workItem;

  // Return it to the caller so that he can await it
  return workItem;
}

现在还有一个问题是谁从字典中删除了该项目。一种方法可以是在创建工作任务时附加一个延续,一旦完成就将其从地图中删除。

workItem.ContinueWith(wi => {
  lock (currentWork) {
    currentWork.Remove(workId);
  }
});