1

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" queue, which provides following functionalities:

  • i have a certain scope for a group of objects. that means that Group A, Group B, ... will have their own queue
  • i'm filling a queue in a group-scoped thread Thread A (Producer)
  • i'm reading a queue in a group-scoped thread Thread B (Consumer)

so i will have following scenarios:

  1. there is and will be no item in the queue (as the jobs were called with an empty "targetgroup"): Thread B should escape the loop
  2. there is currently no item in the queue, as Thread A is working on the item to enqueue: Thread B should wait
  3. there are items in the queue: Thread B should be able to dequeue and process the item
  4. there is no item in the queue, as Thread A has no more items to enqueue: Thread B should escape the loop

now i came up with following implementation:

public class MightyQueue<T>
  where T : class
{
    private readonly Queue<T> _queue = new Queue<T>();

    private bool? _runable;
    private volatile bool _completed;

    public bool Runable
    {
        get
        {
            while (!this._runable.HasValue)
            {
                Thread.Sleep(100);
            }
            return this._runable ?? false;
        }
        set
        {
            this._runable = value;
        }
    }

    public void Enqueue(T item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }

        this._queue.Enqueue(item);
    }

    public void CompleteAdding()
    {
        this._completed = true;
    }

    public bool TryDequeue(out T item)
    {
        if (!this.Runable)
        {
            item = null;
            return false;
        }
        while (this._queue.Count == 0)
        {
            if (this._completed)
            {
                item = null;
                return false;
            }
            Thread.Sleep(100);
        }
        item = this._queue.Dequeue();
        return true;
    }
}

which then would be used

Producer

if (anythingToWorkOn)
{
    myFooMightyQueueInstance.Runable = false;
}
else
{
    myFooMightyQueueInstance.Runable = true;
    while (condition)
    {
        myFooMightyQueueInstance.Enqueue(item);
    }
    myFooMightyQueueInstance.CompleteAdding();
}

Consumer

if (!myFooMightyQueueInstance.Runable)
{
    return;
}

T item;
while (myFooMightyQueueInstance.TryDequeue(out item))
{
    //work with item
}

but i believe, that this approach is wrong, as i'm using some Thread.Sleep()-stuff in there (can't be there some waitHandle or something else?)... i'm not about the algo itself either ... can anyone please help me out?

Community
  • 1
  • 1
  • So, what's your question, what have you tried so far? Sounds to me like a simple Two_Thread structure with a Queue in the middle which needs to be queried from Thread B. – Bobby Nov 10 '10 at 10:18
  • @marcelo: `just wondering how to fully solve this problem` should be clear –  Nov 10 '10 at 10:20
  • 2
    there is absolutely no reason to use threads in such scenario because Thread B will just wait for Thread A, like 90 % of all time + considering the context switching overhead and thread synchronization overhead the result might easily be that the multithread version will be actualy slower then the single threaded version – Marek Szanyi Nov 10 '10 at 10:20
  • @psicho: my problem is not performance, it's about accurance in matter of time. if you need to start mailing at eg. 08h00 and you need an unknown amount of time to prepare the bodies (as you do not exactly know the amount nor the needed time for one body) you always have the option to start eg. 5mins earlier with preparing. so that's the reason for multihreading! –  Nov 10 '10 at 10:23
  • @Andreas: So you're looking for code snippets that solve your problem? Or a class diagram? Or both? You described the scenarios, Marcelo described a structure. There's not much more to do. – MatthiasG Nov 10 '10 at 10:25
  • 1
    @Andreas: SMTP servers can be configured to accept messages for queue, allowing you to offload your threading to the mail server. You're implementing something here that has already been solved. – Codesleuth Nov 10 '10 at 10:26
  • @matthiasg: some code-snippets would be perfect ...! i do not really need a cd, as i believe that this could be done inside a single class –  Nov 10 '10 at 10:30
  • @codesleuth: awful, but it's not that simple ... i will have to work with the items, not just simply mail them via a mail-provider ... i've tried to reduce my specs to a minimum, but please leave my specs/conditions untouched (i'm fully aware of why they are like they are) –  Nov 10 '10 at 10:35
  • @Andreas: I'm not *touching* your specs; I was simply pointing out an alternative solution that already exists. – Codesleuth Nov 10 '10 at 11:35
  • ... which cannot be used in this scenario –  Nov 10 '10 at 12:00

5 Answers5

2

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.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • no option for `.net 4.0`, that's why i've added the tag `.net 3.5` ... sorry :( –  Nov 10 '10 at 10:25
  • additionally, this solutions lacks `issue 1` either. how do deal with queues which will enver get any items ... –  Nov 10 '10 at 10:36
  • who ... bad idea! if i will do this, the personalization may not be valid anymore when sending! i will have to reduce the timespan from personalizing to sending to a minimum! –  Nov 10 '10 at 10:38
  • 1
    Specify the problem more clearly. Your question omits important details that you seem to be metering out in small doses. – Marcelo Cantos Nov 10 '10 at 10:48
  • i should do that, jep, as everyone tries to create a new environment for me :) lol ... will do that later! –  Nov 10 '10 at 10:54
1

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.

dutt
  • 7,909
  • 11
  • 52
  • 85
  • You forgot to lock before calling `queue_empty`. – Marcelo Cantos Nov 10 '10 at 10:31
  • how to deal with `issue 1`: no item will ever be added to the queue? –  Nov 10 '10 at 10:40
  • 1
    @Andreas: Your first point merely states a condition, but fails to explain what you want to do when this condition occurs. If you just want the thread to exit, the usual approach is to poison the queue (i.e., add a "die" message). In this respect, there is nothing special about the special-case of no messages. – Marcelo Cantos Nov 10 '10 at 10:51
  • updated my question .. thanks for that hint - i thought that would be obvious :) wasn't that wrong, as you've nearly guessed it :) –  Nov 10 '10 at 10:53
  • Marcelo no i didn't, that's why I added the comment about qeueue_empty being thread_safe, but yea i could have written it more clearly. I'll add the die-condition, that one i did forget about. – dutt Nov 10 '10 at 23:05
1

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)
vgru
  • 49,838
  • 16
  • 120
  • 201
  • how to deal with `issue 1`: no items will ever be added to the queue? –  Nov 10 '10 at 10:38
  • @Andread Niedermair: It is not completely clear what you expect should happen in that case. Using a simple producer/consumer, thread B will never be notified by thread A. I'll update the answer in a moment. – vgru Nov 10 '10 at 10:41
  • well ... for each big targetgroup there exist one **Thread B** and one **Thread A** ... it's not a global mailing/personalization-thread, as these threads/jobs need to know more about the targetgroup (the real reason for this scope-scenario). see my update :) –  Nov 10 '10 at 10:43
  • @Andreas: Thanks, I am glad if you can use it. Just check the first line in your Worker.Stop() method, there was a typo when detaching the event handler (there must be a `-=` instead of `+=` at the beginning of Stop), I have just noticed it and fixed it. – vgru Nov 11 '10 at 07:39
1

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;
    }
}
MatthiasG
  • 4,434
  • 3
  • 27
  • 47
  • this solution lacks synchronization of `running` and it needs a `Thread.Sleep()` which is not very nice ... but with some modification it gets applicable! –  Nov 10 '10 at 13:54
  • The Tread.Sleep() is just for demonstration purposes, you can remove it safely ;) Otherwise you wouldn't see, that mails are checked periodically and you can interrupt it by pressing a key. And I didn't use locks or other synchronization mechanisms because I don't think there is a need for your purposes. – MatthiasG Nov 10 '10 at 14:26
0

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.

Shiv Kumar
  • 9,599
  • 2
  • 36
  • 38
  • read my comments (i have a good reason for multithreading)! ... this question is not about the design of the 2 threads, it's about the design of the queue! ... –  Nov 10 '10 at 10:32
  • I see that you've edited your question - however, I still don't see a question in there. Are you sure your read for multi-threading is a good one given the various other responses? It seems to me that you expect all your email to go out at an exact time. That will need a whole lot of bandwidth on your server side. I've generated and sent 100,000 emails in about 5 minutes but the emails sit in the smtp server for over an hour because the actual send of an email takes a long time and you need a boot load of bandwidth around 20GB/sec. – Shiv Kumar Nov 10 '10 at 10:45
  • just digg deeper: the timespan between personalization and mailing must be reduced to a minimum. otherwise the personalization won't as accurate as possible ... i don't care about bandwith, this won't be an issue, as this is a more general problem (as i believe). –  Nov 10 '10 at 10:48
  • Personalization and generating the email will take no time in comparision to actually sending the email. What do you consider to be "mailing". If by mailing you mean the email has truly been sent, then you'll be in for a surprise 'cause that takes a lot of time and depending on the number of emails that are going out... Anyway, I still don't quite get your question so I'll just back off. – Shiv Kumar Nov 10 '10 at 10:56