2

THE SCENARIO:

I want to ask this question regarding Parallel.For(or any other multithreading approach in C#.Net). I have to build a MultiThreaded Mailer Windows service that will send mails to all the recipients as fast as it can. I get the serialized rows from the database that contains the email message and SmtpDetails and then deSerialize them in code.

An emails may have 1000 reciepients and so on a Dual Core machine ( development machine) at least 2 threads can run simultaneously. So i use parallel.For in order ro do this. I have read about the LocalInit delegate that runs once for every thread.

THE CODE:

int itemCount = serMailObj.ReceipientList.Count;

                    Parallel.For(0, itemCount, () =>
                    {
                        return new ThreadLocalStateCache()
                        {
                            Receipient = serMailObj.ReceipientList.Dequeue(),
                            mail = serMailObj.Email,
                            SerializableSmtpDetails = serSmtpObj
                        };
                    }
     , doWork, (x) => { });

 private static ThreadLocalStateCache doWork(int instance, ParallelLoopState state, ThreadLocalStateCache threadInstance)
        {
            KeyValuePair<string, string> kvp = threadInstance.Receipient;
            SerializableSmtpDetails serSmtpObj = threadInstance.SerializableSmtpDetails;
            MailMessage email = threadInstance.mail;

                email.To.Add(new MailAddress(kvp.Key, kvp.Value));

                SmtpClient client = new SmtpClient();
                client.Credentials = new System.Net.NetworkCredential(serSmtpObj.UserName, serSmtpObj.Password);
                client.Host = serSmtpObj.Host;
                client.Port = serSmtpObj.Port;
                client.EnableSsl = serSmtpObj.EnableSSL;

                    try 
                    {           
                        client.Send(email);
                        Console.WriteLine("sending mail....");
                    }
                    catch (Exception)
                    {

                        throw;
                    }


            return null;
        }

public class ThreadLocalStateCache
    {
        public KeyValuePair<string, string> Receipient { get; set; }

        public MailMessage mail { get; set; }

        public SerializableSmtpDetails SerializableSmtpDetails { get; set; }
    }

The above code is pretty straight forward. The localInit delegate constructs a local object foreach thread. and then the doWork tries to process the queue.

THE PROBLEMS:

  1. I am getting multiple mails for each recipient. seems as if the email object is being shared among threads.

  2. getting failure sending mail sometimes.

Kindly explain as to how i can isolate the mail and smtpclient objects in each thread. and process the queue.

EDIT 1: If the multithreading gurus would help me please tell that is there any way for every thread to have a unique copy of its local variables and not shared ones. Since the MailMessage object is not immutable i cannot create a clone of it also. apart from deseralizing it in each thread(which would ensure a new object is created) is there any magic way to achieve this?

Pankaj Kumar
  • 1,748
  • 6
  • 28
  • 41

2 Answers2

1

below might be the issue:

serMailObj.ReceipientList.Dequeue()

Try using ConcurrentQueue (.NET 4) or put locks around so that one thread at a time can Dequeue it.

also make to sure Concurrent classes or locks anywhere yo uhave access to shared resource from threads.

A Queue <(Of <(T >)>) can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Bek Raupov
  • 3,782
  • 3
  • 24
  • 42
  • Assuming my understanding is correct..since the dequeue is taking place in LocalInit delegate it should run only once for each thread. I am more worried about the email object that is being shared. – Pankaj Kumar Apr 11 '11 at 12:14
  • yes, but if multiple threads try to dequeue at the same time, it might become inconsistent state. best to use ConcurrentQueue i would think – Bek Raupov Apr 11 '11 at 12:19
  • have you considered using ThreadPool for this ? not sure if you get huge benefit of using Parellel tasks for this. – Bek Raupov Apr 11 '11 at 12:20
  • yes..i have tried using ThreadPool for this. but since i am a newbie in multithreading i cant understand the code that i got for that. Got the code from SO itself. http://stackoverflow.com/questions/5101891/how-can-i-send-mailmessages-using-multiple-threads. The Interlock and decrement bits of the code. parallel.for seems much easy to me since it takes care of the synchronization for me. – Pankaj Kumar Apr 11 '11 at 12:27
1

There might be problems due to doWork() returning null. As I learned when answering your recent comment here, the thread local object should be passed between subsequent invocations of the Parallel.For body at the same thread, because it is supposed to work as an accumulator; see the usage example at MSDN. It's unclear what happens when you return null, but I would fix that and see whether it makes difference.

Community
  • 1
  • 1
Alexey Kukanov
  • 12,479
  • 2
  • 36
  • 55