生产者消费者模式您如何通知完成?

时间:2011-11-16 18:09:15

标签: c# multithreading

使用.net 2.0我需要实现一些线程,我正在查看一个虚拟示例,但找不到任何实现事件通知的内容。 需要知道什么时候完成,如果你愿意,也需要某种进度条。

我一直在玩以下代码无法正常获取事件通知。 我怎么检测到我已完成处理并可能用我正在做的事情更新ui?

示例代码

 class Program
{
    static void Main(string[] args)
    {
        using (PCQueue q = new PCQueue(2))
        {
            q.TaskCompleted += new EventHandler(OnTaskCompleted);
            q.PercentageCompleted += new EventHandler(OnPercentageCompleted);


            for (int i = 1; i < 100; i++)
            {

                string itemNumber = i.ToString(); // To avoid the captured variable trap
                q.EnqueueItem(itemNumber);
            }
          Console.WriteLine("Waiting for items to complete...");
            Console.Read();
        }
    }

    private static void OnPercentageCompleted(object sender, EventArgs e)
    {

    }

    static void OnTaskCompleted(object sender, EventArgs e)
    {

    }
}


public class PCQueue : IDisposable
{
    readonly object locker = new object();
    Thread[] _workers;
    Queue<string> _itemQ = new Queue<string>();
    public PCQueue(int workerCount)
    {
        _workers = new Thread[workerCount];
        // Create and start a separate thread for each worker
        for (int i = 0; i < workerCount; i++)
        {
            (_workers[i] = new Thread(Consume)).Start();
        }
    }

    public void EnqueueItem(string item)
    {
        lock (locker)
        {
            _itemQ.Enqueue(item); // We must pulse because we're
            Monitor.Pulse(locker); // changing a blocking condition.
        }
    }
    void Consume()
    {
        while (true) // Keep consuming until
        { // told otherwise.
            string item;
            lock (locker)
            {
                while (_itemQ.Count == 0) Monitor.Wait(locker);
                item = _itemQ.Dequeue();
            }
            if (item == null) return; // This signals our exit.

            DoSomething(item); // Execute item.
        }
    }
    private void DoSomething(string item)
    {
       Console.WriteLine(item);
    }
    public void Dispose()
    {
        // Enqueue one null item per worker to make each exit.
        foreach (Thread worker in _workers)
        {
            EnqueueItem(null);
        }
    }

    //where/how  can I fire this event???
    public event EventHandler TaskCompleted;
    protected void OnCompleted(EventArgs e)
    {
        if (this.TaskCompleted != null)
        {
            this.TaskCompleted(this, e);
        }
    }
    //where/how can I fire this event???
    public event EventHandler PercentageCompleted;
    protected void OnPercentageCompleted(EventArgs e)
    {
        if (this.PercentageCompleted != null)
        {
            this.PercentageCompleted(this, e);
        }
    }
   }

有什么建议吗?

2 个答案:

答案 0 :(得分:2)

您无法在队列中引发progress事件,原因很简单,因为队列不知道应该处理的项目总数。所以它无法计算百分比。你只需坚持一些东西就可以得到处理。

你可以做的是提出一个ItemProcessed事件并订阅它。然后在你的主程序中,你可以做一个逻辑,计算到目前为止处理的项目数与处理的数量有关。

您可以在从Consume功能返回之前提升完整事件。但是,正如布莱恩在回答中所说,你需要跟踪有多少线程仍处于活动状态。我修改了代码以反映出来。

这些内容如下:

...
private int _ActiveThreads;
public PCQueue(int workerCount)
{
    _ActiveThreads = workerCount;
    _workers = new Thread[workerCount];
    // Create and start a separate thread for each worker
    for (int i = 0; i < workerCount; i++)
    {
        (_workers[i] = new Thread(Consume)).Start();
    }
}

void Consume()
{
    while (true) // Keep consuming until
    { // told otherwise.
        string item;
        lock (locker)
        {
            while (_itemQ.Count == 0) Monitor.Wait(locker);
            item = _itemQ.Dequeue();
        }
        if (item == null)  // This signals our exit.
        {
            if (Interlocked.Decrement(ref _ActiveThreads) == 0)
            {
                OnCompleted(EventArgs.Empty);
            }
            return;
        }

        DoSomething(item); // Execute item.
        OnItemProcessed();
    }
}

public event EventHandler ItemProcessed;
protected void OnItemProcessed()
{
    var handler = ItemProcessed;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
...

当然,你可能想要创建一些有意义的事件args并实际传递处理到事件的项目。

然后在主要:

...
static void Main(string[] args)
{
    using (PCQueue q = new PCQueue(2))
    {
        q.ItemProcessed += ItemProcessed;
        q.TaskCompleted += OnTaskCompleted;

        for (int i = 1; i <= totalNumberOfItems; i++)
        {
            string itemNumber = i.ToString(); // To avoid the captured variable trap
            q.EnqueueItem(itemNumber);
        }
        Console.WriteLine("Waiting for items to complete...");
        Console.Read();
    }
}

private static int currentProcessCount = 0;
private static int totalNumberOfItems = 100;

private static void ItemProcessed(object sender, EventArgs e)
{
     currentProcessCount++;
     Console.WriteLine("Progress: {0}%", ((double)currentProcessCount / (double)totalNumberOfItems) * 100.0);
}

static void OnTaskCompleted(object sender, EventArgs e)
{
     Console.WriteLine("Done");
}
...

毋庸置疑,所有静态的东西都应该去。这只是基于你的例子。

还有一句话: 您的PCQueue当前要求您将多个null值排队,因为您拥有工作线程,否则只有一个线程将退出,其他线程将等待您的进程退出。你可以通过查看第一个项目来改变它,只有当它不是null时才删除它 - 从而将标记留在那里用于所有线程。所以Consume会改为:

void Consume()
{
    while (true) // Keep consuming until
    { // told otherwise.
        string item;
        lock (locker)
        {
            while (_itemQ.Count == 0) Monitor.Wait(locker);
            item = _itemQ.Peek();
            if (item != null) _itemQ.Dequeue();
            else Monitor.PulseAll(); // if the head of the queue is null then make sure all other threads are also woken up so they can quit
        }
        if (item == null)  // This signals our exit.
        {
            if (Interlocked.Decrement(ref _ActiveThreads) == 0)
            {
                OnCompleted(EventArgs.Empty);
            }
            return;
        }

        DoSomething(item); // Execute item.
        OnItemProcessed();
    }
}

答案 1 :(得分:2)

PCQueue类中,您需要跟踪有多少工作线程仍处于活动状态,并且只有在指示所有线程结束后才会引发TaskCompleted

void Consume()
{
    while (true)
    {
        string item;
        lock (locker)
        {
            while (_itemQ.Count == 0) Monitor.Wait(locker);
            item = _itemQ.Dequeue();
        }

        if (item == null)
        {
            // activeThreads is set to the number of workers in the constructor.
            if (Interlocked.Decrement(ref activeThreads) == 0)
            {
              // Take a snapshot of the event so that a null check + invocation is safe.
              // This works because delegates are immutable.
              var copy = TaskCompleted;

              if (copy != null)
              {
                copy(this, new EventArgs());
              }
            }
            return;
        }

        DoSomething(item); // Execute item.
    }
}

其他几点:

  • 获得正确实施阻止队列的荣誉。大多数人都弄错了。
  • 在触摸任何UI控件之前,请记得将TaskCompleted事件处理程序封送回UI线程。
  • 您可以从PercentCompleted引发DoSomething,但没有明确指出队列假设保留该值的项数将没有意义。关于这一点,我第二次Chris' recommendation