串行处理ConcurrentQueue并限制为一个消息处理器。正确的模式?

时间:2011-12-23 19:08:27

标签: c# multithreading c#-4.0 concurrency

我正在.net中构建一个多线程应用程序。

我有一个监听连接的线程(abstract,serial,tcp ...)。

当收到新消息时,它会通过AddMessage将其添加到消息中。然后调用startSpool。 startSpool检查假脱机是否已经运行,如果是,则返回,否则,在新线程中启动它。原因是,消息必须按顺序处理,FIFO。

所以,我的问题是...... 我是以正确的方式来做这件事的吗? 那里有更好,更快,更便宜的模式吗?

如果我的代码中有拼写错误,我很抱歉,我在复制和粘贴方面遇到了问题。

    ConcurrentQueue<IMyMessage > messages = new ConcurrentQueue<IMyMessage>();

    const int maxSpoolInstances = 1;

    object lcurrentSpoolInstances;
    int currentSpoolInstances = 0;

    Thread spoolThread;

    public void AddMessage(IMyMessage message)
    {
        this.messages.Add(message);

        this.startSpool();
    }

    private void startSpool()
    {
        bool run = false;

        lock (lcurrentSpoolInstances)
        {
            if (currentSpoolInstances <= maxSpoolInstances)
            {
                this.currentSpoolInstances++;
                run = true;
            }
            else
            {
                return;
            }
        }

        if (run)
        {
            this.spoolThread = new Thread(new ThreadStart(spool));
            this.spoolThread.Start();
        }
    }

    private void spool()
    {
        Message.ITimingMessage message;

        while (this.messages.Count > 0)
        {
            // TODO: Is this below line necessary or does the TryDequeue cover this?
            message = null;

            this.messages.TryDequeue(out message);

            if (message != null)
            {
                // My long running thing that does something with this message.
            }
        }


        lock (lcurrentSpoolInstances)
        {
            this.currentSpoolInstances--;
        }
    }

3 个答案:

答案 0 :(得分:4)

使用BlockingCollection<T>代替ConcurrentQueue<T>会更容易。

这样的事情应该有效:

class MessageProcessor : IDisposable
{
    BlockingCollection<IMyMessage> messages = new BlockingCollection<IMyMessage>();

    public MessageProcessor()
    {
       // Move this to constructor to prevent race condition in existing code (you could start multiple threads...
       Task.Factory.StartNew(this.spool, TaskCreationOptions.LongRunning);
    }

    public void AddMessage(IMyMessage message)
    {
        this.messages.Add(message);
    }

    private void Spool()
    {
         foreach(IMyMessage message in this.messages.GetConsumingEnumerable())
         {
               // long running thing that does something with this message.
         }
    }

    public void FinishProcessing()
    {
         // This will tell the spooling you're done adding, so it shuts down
         this.messages.CompleteAdding();
    }

    void IDisposable.Dispose()
    {
        this.FinishProcessing();
    }
}

编辑:如果您想支持多个消费者,您可以通过单独的构造函数来处理它。我将其重构为:

    public MessageProcessor(int numberOfConsumers = 1)
    {
        for (int i=0;i<numberOfConsumers;++i)
            StartConsumer();
    }

    private void StartConsumer()
    {
       // Move this to constructor to prevent race condition in existing code (you could start multiple threads...
       Task.Factory.StartNew(this.spool, TaskCreationOptions.LongRunning);
    }

这将允许您启动任意数量的消费者。请注意,这会破坏严格使用FIFO的规则 - 处理可能会在此更改的情况下处理块中的“numberOfConsumer”元素。

已支持多个制作者。以上是线程安全的,因此任意数量的线程都可以并行调用Add(message),而不做任何更改。

答案 1 :(得分:1)

我认为Reed的答案是最好的方法,但是为了学术,这里有一个使用并发队列的例子 - 你发布的代码中有一些比赛(取决于你如何处理增加currnetSpoolInstances )

我所做的更改(如下)是:

  • 切换到任务而不是线程(使用线程池而不是产生创建新线程的成本)
  • 添加了代码以增加/减少假脱机实例数
  • 将“if currentSpoolInstances&lt; = max ...”更改为&lt;以避免让一个工人太多(可能只是一个错字)
  • 改变了处理空队列以避免竞争的方式:我认为你有一个种族,你的while循环可能已经测试为false,(你的线程开始退出),但是在那一刻,添加了一个新项目(所以你的假脱机线程正在退出,但你的假脱机数量> 0,所以你的队列停滞了。)

private ConcurrentQueue<IMyMessage> messages = new ConcurrentQueue<IMyMessage>();

const int maxSpoolInstances = 1;
object lcurrentSpoolInstances = new object();
int currentSpoolInstances = 0;

public void AddMessage(IMyMessage message)
{
    this.messages.Enqueue(message);
    this.startSpool();
}

private void startSpool()
{
    lock (lcurrentSpoolInstances)
    {
        if (currentSpoolInstances < maxSpoolInstances)
        {
            this.currentSpoolInstances++;
            Task.Factory.StartNew(spool, TaskCreationOptions.LongRunning);
        }
    }
}

private void spool()
{
    IMyMessage message;
    while (true)
    {
// you do not need to null message because it is an "out" parameter, had it been a "ref" parameter, you would want to null it.

        if(this.messages.TryDequeue(out message))
        {
            // My long running thing that does something with this message. 
        }
        else
        {
            lock (lcurrentSpoolInstances)
            {
                if (this.messages.IsEmpty)
                {
                    this.currentSpoolInstances--;
                    return;
                }
            }
        }
    }
} 


答案 2 :(得分:0)

检查&#39;管道模式&#39;:http://msdn.microsoft.com/en-us/library/ff963548.aspx

  • 使用BlockingCollection作为&#39;缓冲区&#39;。
  • 每个处理器(例如ReadStrings,CorrectCase,..)应该在任务中运行。

enter image description here

HTH ..