4

我正在做一个小练习,我需要创建类似于消息泵的东西。我有一个工作队列要做,我希望工作完全在一个线程上完成,而任何线程都可以将工作添加到要完成的队列中。

Queue<WorkToDo> queue;

线程使用等待句柄告诉泵有工作要做。

WaitHandle signal;

只要有工作要做,泵就会循环,然后等待信号再次启动。

while(ApplicationIsRunning){
    while(queue.HasWork){
        DoWork(queue.NextWorkItem)
    }
    signal.Reset();
    signal.WaitOne();
}

每个其他线程都可以向队列添加工作并发出等待句柄的信号......

public void AddWork(WorkToDo work){
    queue.Add(work);
    signal.Set();
} 

问题是,如果添加工作的速度足够快,则可能会出现这样一种情况,即工作可以留在队列中,因为在队列检查工作和 WaitHandle 重置之间,另一个线程可以将工作添加到队列中。

我将如何减轻这种情况而不在 WaitHandle 周围放置昂贵的互斥锁?

4

3 回答 3

3

您可以使用它来BlockingCollection<T>轻松地实现队列,因为它将为您处理同步:

public class MessagePump
{
    private BlockingCollection<Action> actions = new BlockingCollection<Action>();

    public void Run() //you may want to restrict this so that only one caller from one thread is running messages
    {
        foreach (var action in actions.GetConsumingEnumerable())
            action();
    }

    public void AddWork(Action action)
    {
        actions.Add(action);
    }

    public void Stop()
    {
        actions.CompleteAdding();
    }
}
于 2013-02-19T20:54:30.373 回答
2

你不应该做一个完整的互斥锁,但你可以放一个锁语句

public void AddWork(WorkToDo work)
{
  queue.Add(work);
  lock(lockingObject)
  {
    signal.Set();
  }
} 

使用任何你想要的锁定对象,大多数人会说使用信号本身是一个坏主意。

响应下面的@500 - 服务器内部错误的评论,您可以在工作之前重置信号。以下应该保护的东西:

while(ApplicationIsRunning)
{
  while(queue.HasWork)
  {
    WorkItem wi;
    lock(lockingObject)
    {
       wi = queue.NextWorkItem;
       if(!queue.HasWork)
       {
          signal.Reset();
       }
    }
    DoWork(wi)
  }

  signal.WaitOne();
}

这样,如果您有更多工作,内部队列将继续进行。如果没有,它会落入signal.WaitOne(),并且只有在没有更多工作排队时我们才会重置。

这里唯一的缺点是,如果在执行时有工作进入,我们可能会连续多次重置DoWork

于 2013-02-19T20:10:41.377 回答
0

您可以使用WaitOne(TimeSpan)方法,以便拥有混合信号/轮询循环。基本上指定最多等待 1 秒的时间跨度。这将导致任务在该竞赛中被捕获最多保留一秒钟(或您指定的任何轮询时间)或直到另一个任务添加到您的队列中。

于 2013-02-19T20:09:16.167 回答