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

前端 未结 5 1826
无人共我
无人共我 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:29

    I wrote an easy example that works fine for me and should be suitable for your scenarios. If the consumer is running is depending of how the running variable is set, but you easily modify it to a more complex condition like "if no mail exists but someone said I should wait for more".

    public class MailSystem
    {
        private readonly Queue<Mail> mailQueue = new Queue<Mail>();
        private bool running;
        private Thread consumerThread;
    
        public static void Main(string[] args)
        {
            MailSystem mailSystem = new MailSystem();
            mailSystem.StartSystem();
        }
    
        public void StartSystem()
        {
            // init consumer
            running = true;
            consumerThread = new Thread(ProcessMails);
            consumerThread.Start();
            // add some mails
            mailQueue.Enqueue(new Mail("Mail 1"));
            mailQueue.Enqueue(new Mail("Mail 2"));
            mailQueue.Enqueue(new Mail("Mail 3"));
            mailQueue.Enqueue(new Mail("Mail 4"));
            Console.WriteLine("producer finished, hit enter to stop consumer");
            // wait for user interaction
            Console.ReadLine();
            // exit the consumer
            running = false;
            Console.WriteLine("exited");
        }
    
        private void ProcessMails()
        {
            while (running)
            {
                if (mailQueue.Count > 0)
                {
                    Mail mail = mailQueue.Dequeue();
                    Console.WriteLine(mail.Text);
                    Thread.Sleep(2000);
                }
            }
        }
    }
    
    internal class Mail
    {
        public string Text { get; set; }
    
        public Mail(string text)
        {
            Text = text;
        }
    }
    
    0 讨论(0)
  • 2021-01-24 01:32

    I'd use one thread to job the entire process. That is generating the mail body and sending. Simply because generating the mail body won't take time, but send the email will.

    Also if you are using the SMTP server that comes with Windows, then you can simply drop your email in the queue folder and the smtp server will take care of sending the emails.

    So you could start multiple threads (keeping a cap on the number) where each thread does it's job. If you're working with sat a collection of jobs (the data) then you could data parallelize (that is split the data into chunks that match the number of core on the system for example and shoot of the jobs (the threads).

    Using Tasks will make all of this quite a bit simpler either way you go, that is 2 threads to send one email or one thread to do the entire job but using multiple threads to do multiple jobs in parallel.

    0 讨论(0)
  • 2021-01-24 01:33

    If you have .Net 4.0, use a BlockingCollection. It deals with all the mess for you, including the final point, via the CompleteAdding method.

    If you have an earlier .Net, then upgrade (i.e., I'm too lazy to explain how to implement something that has already been done for you.)

    EDIT: I don't think your problem warrants threading at all. Just create all the emails well in advance and then sleep till the appointed time.

    0 讨论(0)
  • 2021-01-24 01:40

    What you want could be done with conditionvariables. I'll compose a pseudo-code example, shouldn't be too hard to implement.

    One thread has something along the lines of:

    while(run)
      compose message
      conditionvariable.lock()
      add message to queue
      conditionvariable.notifyOne()
      conditionvariable.release()
    

    While the other thread has something along these lines

    while(threadsafe_do_run())
      while threadsafe_queue_empty()
           conditionvariable.wait()
      msg = queue.pop()
      if msg == "die"
          set_run(false)
      conditionvariable.release()
      send msg
    

    So if you don't get any messages push a die-message. Same thing when all messages have been processed.

    do_run() and queue_empty() should check their things thread-safely, use appropriate locks.

    wait() returns when notifyOne() is called and then the queue has a msg to send. in most frameworks the conditionvariable already has the lock, you might need to add the lock-statement yourself in .NET.

    0 讨论(0)
  • 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<T> : IStoppable
    {
        /// <summary>
        /// Notifies clients when a new item is produced.
        /// </summary>
        event EventHandler<ProducedItemEventArgs<T>> ItemProduced;
    }
    
    public interface IConsumer<T> : IStoppable
    {
        /// <summary>
        /// Performs processing of the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        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<T>
    {
        private readonly Object _lock = new Object();
        private readonly Queue<T> _queuedItems = new Queue<T>();
        private readonly AutoResetEvent _itemReadyEvt = new AutoResetEvent(false);
        private readonly IProducer<T> _producer;
        private readonly IConsumer<T> _consumer;
        private volatile bool _ending = false;
        private Thread _workerThread;
    
        public Worker(IProducer<T> producer, IConsumer<T> 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<T> 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<IMail> mailCreator = new MailCreator();
    IConsumer<IMail> mailSender = new MailSender();
    
    Worker<IMail> worker = new Worker<IMail>(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)
    0 讨论(0)
提交回复
热议问题