C#在Web服务中实现生产者/使用者队列

时间:2012-05-11 14:14:42

标签: c# web-services producer-consumer

我有一个相当普通的网络服务(旧学校asmx)。其中一种方法启动了一些与返回给客户端的结果无关的异步处理。希望下面的小片段有意义:

[System.Web.Services.WebMethod]
public List<Foo> SampleWebMethod(string id)
{
    // sample db query
    var foo = db.Query<Foo>("WHERE id=@0",id);

    // kick of async stuff here - for example firing off emails
    // dont wait to send result
    DoAsyncStuffHere();

    return foo;

}

我对DoAsyncStuffHere方法的初始实现使用了ThreadPool.QueueUserWorkItem。所以,它看起来像:

public void DoAsyncStuffHere()
{
    ThreadPool.QueueUserWorkItem(delegate
    {
        // DO WORK HERE
    });
}

这种方法在低负载条件下工作正常。但是,我需要能够处理相当高负载的东西。因此,生产者/消费者模式似乎是最好的方式。

我感到困惑的是如何将队列所做的所有工作限制在Web服务的所有实例 的 单线程中。我如何才能最好地设置单个队列以供任何Web服务实例访问?

3 个答案:

答案 0 :(得分:5)

您可以使用System.Collections.Concurrent.BlockingCollection<T>作为基础集合System.Collections.Concurrent.ConcurrentQueue<T> 正如名称空间的名称所暗示的那样,集合是线程安全的。

使用Take()方法启动一个消费者线程(或几个)从集合中提取项目。当没有可用的项目时,线程将阻止。

您的DoAsyncStuffHere方法将项添加到BlockingCollection。这些项目可能是未启动的System.Threading.Tasks.Task个对象;在这种情况下,消费者线程将Start从集合中获取后的任务。

答案 1 :(得分:1)

一种简单的方法是将队列实现为数据库表。

生产者将是每个Web服务实例处理的请求线程。

消费者可以是任何类型的持续运行的进程(Windows窗体应用程序,Windows服务,数据库作业等),它们监视队列并一次处理一个项目。

答案 2 :(得分:1)

你不能用ThreadPool做到这一点 - 你可以有一个静态构造函数来启动一个工作线程; DoAsyncStuffHere可以将其工作项插入到您想要完成的工作队列中,并且工作线程可以检查队列中是否有任何项目可以处理。如果是这样,那就完成了工作,否则就会睡几毫秒。

静态构造函数确保它只被调用一次,并且只应启动一个Thread(除非有一些我不知道的奇怪的.NET边缘情况)。

这是一个示例的布局 - 您可能需要在队列上实现一些锁定并为工作线程添加更多复杂性,但我在成功之前使用了此模式。 WorkItem对象可以保存您希望传递给工作线程的信息的状态。

public static WebService() 
{
  new Thread(WorkerThread).Start();
  WorkQueue = new Queue<WorkItem>();
}

public static void WorkerThread() 
{
  while(true)
  {
    if(WorkQueue.Any()) 
    {
      WorkQueue.Dequeue().DoWork();
    }
    else
    {
      Thread.Sleep(100);
    }
  }
}

public static Queue<WorkItem> WorkQueue { get; set; }

[System.Web.Services.WebMethod]
public List<Foo> SampleWebMethod(string id)
{
  WorkQueue.Queue(newWorkItem());
}