I have a console application that sends customized emails (with attachments) to different recipients and I want to send them concurrently. I need to create separate SmtpClients to achieve this so I am using QueueUserWorkItem to create the emails and send them in separate threads.
Snippet
var events = new Dictionary<Guid, AutoResetEvent>();
foreach (...)
{
ThreadPool.QueueUserWorkItem(delegate
{
var id = Guid.NewGuid();
events.Add(id, new AutoResetEvent(false));
var alert = // create custom class which internally creates SmtpClient & Mail Message
alert.Send();
events[id].Set();
});
}
// wait for all emails to signal
WaitHandle.WaitAll(events.Values.ToArray());
I have noticed (intermittently) that sometimes not all the emails arrive in the specific mailboxes with the above code. I would have thought that using Send
over SendAsync
would mean the email has definitely sent from the application. However, adding the following line of code after the WaitHandle.WaitAll
line:
System.Threading.Thread.Sleep(5000);
Seems to work. My thinking is, for whatever reason, some emails still haven't been sent (even after the Send
method has run). Giving those extra 5 seconds seems to give the application enough time to finish.
Is this perhaps an issue with the way I am waiting on the emails to send? Or is this an issue with the actual Send method? Has the email definitely been sent from the app once we pass this line?
Any thoughts idea's on this would be great, can't quite seem to put my finger on the actual cause.
Update
As requested here is the SMTP code:
SmtpClient client = new SmtpClient("Host");
FieldInfo transport = client.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo authModules = transport.GetValue(client).GetType()
.GetField("authenticationModules", BindingFlags.NonPublic | BindingFlags.Instance);
Array modulesArray = authModules.GetValue(transport.GetValue(client)) as Array;
modulesArray.SetValue(modulesArray.GetValue(2), 0);
modulesArray.SetValue(modulesArray.GetValue(2), 1);
modulesArray.SetValue(modulesArray.GetValue(2), 3);
try
{
// create mail message
...
emailClient.Send(emailAlert);
}
catch (Exception ex)
{
// log exception
}
finally
{
emailAlert.Dispose();
}
In the .NET Framework version 2.0, the Thread.CurrentPrincipal property value is propagated to worker threads queued using the QueueUserWorkItem method. In earlier versions, the principal information is not propagated. Queues a method specified by an Action<T> delegate for execution, and provides data to be used by the method.
Queues a work item to a worker thread in the thread pool. A pointer to the application-defined callback function of type LPTHREAD_START_ROUTINE to be executed by the thread in the thread pool. This value represents the starting address of the thread. This callback function must not call the TerminateThread function.
The method executes when a thread pool thread becomes available. Queues a method for execution, and specifies an object containing data to be used by the method. The method executes when a thread pool thread becomes available. Queues a method specified by an Action<T> delegate for execution, and provides data to be used by the method.
When you queue up your background threads if you have enough cpu capacity (or code UpdateDatabase using async to release the thread while waiting) you can try to open more connections to the database than there are in the connection pool.
One of the things that's bugging me about your code is that you call events.Add
within the thread method. The Dictionary<TKey, TValue>
class is not thread-safe; this code should not be inside the thread.
Update: I think ChaosPandion posted a good implementation, but I would make it even simpler, make it so nothing can possibly go wrong in terms of thread-safety:
var events = new List<AutoResetEvent>();
foreach (...)
{
var evt = new AutoResetEvent();
events.Add(evt);
var alert = CreateAlert(...);
ThreadPool.QueueUserWorkItem(delegate
{
alert.Send();
evt.Set();
});
}
// wait for all emails to signal
WaitHandle.WaitAll(events.ToArray());
I've eliminated the dictionary completely here, and all of the AutoResetEvent
instances are created in the same thread that later performs a WaitAll
. If this code doesn't work, then it must be a problem with the e-mail itself; either the server is dropping messages (how many are you sending?) or you're trying to share something non-thread-safe between Alert
instances (possibly a singleton or something declared statically).
You probably want to do this...
var events = new Dictionary<Guid, AutoResetEvent>();
foreach (...)
{
var id = Guid.NewGuid();
events.Add(id, new AutoResetEvent(false));
ThreadPool.QueueUserWorkItem((state) =>
{
// Send Email
events[(Guid)state].Set();
}, id);
}
// wait for all emails to signal
WaitHandle.WaitAll(events.Values.ToArray());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With