How do I implement my own advanced Producer/Consumer scenario?

前端 未结 5 1830
无人共我
无人共我 2021-01-24 00:56

NOTE:
i did a complete rework of my question. you can see the original question via the change-history.


i\'m in the need of a "mighty&qu

5条回答
  •  一向
    一向 (楼主)
    2021-01-24 01:43

    You should begin with a generic Producer-Consumer queue and use that. Implementing this inside a Queue is not such a good idea, as this prevents you from using semaphores to signal threads (or, you could have public semaphores in your Queue, but that's a really bad idea).

    As soon as the thread A has enqueued a single work item, it must signal a semaphore to notify thread B. When thread B has finished processing all items, it should signal a semaphore to notify everyone else that it has finished. Your main thread should be waiting for this second semaphore to know that everything is done.

    [Edit]

    First, you have a producer and a consumer:

    public interface IProducer : IStoppable
    {
        /// 
        /// Notifies clients when a new item is produced.
        /// 
        event EventHandler> ItemProduced;
    }
    
    public interface IConsumer : IStoppable
    {
        /// 
        /// Performs processing of the specified item.
        /// 
        /// The item.
        void ConsumeItem(T item);
    }
    
    public interface IStoppable
    {
        void Stop();
    }
    

    So, in your case, class creating the mail will need to fire the ItemProduced event, and the class sending it will need to implement ConsumeItem.

    And then you pass these two instances to an instance of Worker:

    public class Worker
    {
        private readonly Object _lock = new Object();
        private readonly Queue _queuedItems = new Queue();
        private readonly AutoResetEvent _itemReadyEvt = new AutoResetEvent(false);
        private readonly IProducer _producer;
        private readonly IConsumer _consumer;
        private volatile bool _ending = false;
        private Thread _workerThread;
    
        public Worker(IProducer producer, IConsumer consumer)
        {
            _producer = producer;
            _consumer = consumer;
        }
    
        public void Start(ThreadPriority priority)
        {
            _producer.ItemProduced += Producer_ItemProduced;
            _ending = false;
    
            // start a new thread
            _workerThread = new Thread(new ThreadStart(WorkerLoop));
            _workerThread.IsBackground = true;
            _workerThread.Priority = priority;
            _workerThread.Start();
        } 
    
        public void Stop()
        {
            _producer.ItemProduced -= Producer_ItemProduced;
            _ending = true;
    
            // signal the consumer, in case it is idle
            _itemReadyEvt.Set();
            _workerThread.Join();
        }
    
        private void Producer_ItemProduced
             (object sender, ProducedItemEventArgs e)
        {
            lock (_lock) { _queuedItems.Enqueue(e.Item); }
    
            // notify consumer thread
            _itemReadyEvt.Set();
        }
    
        private void WorkerLoop()
        {
            while (!_ending)
            {
                _itemReadyEvt.WaitOne(-1, false);
    
                T singleItem = default(T);
                lock (_lock)
                {
                   if (_queuedItems.Count > 0)
                   {
                       singleItem = _queuedItems.Dequeue();
                   }
                }
    
    
                while (singleItem != null)
                {
                    try
                    {
                        _consumer.ConsumeItem(singleItem);
                    }
                    catch (Exception ex)
                    {
                        // handle exception, fire an event
                        // or something. Otherwise this
                        // worker thread will die and you
                        // will have no idea what happened
                    }
    
                    lock (_lock)
                    {
                        if (_queuedItems.Count > 0)
                        {
                            singleItem = _queuedItems.Dequeue();
                        }
                    }
                }
    
             }
    
        } // WorkerLoop
    
    } // Worker
    

    That's the general idea, there may be some additional tweaks needed.

    To use it, you need to have your classes implement these two interfaces:

    IProducer mailCreator = new MailCreator();
    IConsumer mailSender = new MailSender();
    
    Worker worker = new Worker(mailCreator, mailSender);
    worker.Start();
    
    // produce an item - worker will add it to the
    // queue and signal the background thread
    mailCreator.CreateSomeMail();
    
    // following line will block this (calling) thread
    // until all items are consumed
    worker.Stop();
    

    The great thing about this is that:

    • you can have as many workers you like
    • multiple workers can accept items from the same producer
    • multiple workers can dispatch items to the same consumer (although this means you need to take case that consumer is implemented in a thread safe manner)

提交回复
热议问题