Reputation: 9881
I have created a newsletter system that allows me to specify which members should receive the newsletter. I then loop through the list of members that meet the criteria and for each member, I generate a personalized message and send them the email asynchronously .
When I send out the email, I am using ThreadPool.QueueUserWorkItem
.
For some reason, a subset of the members are getting the email twice. In my last batch, I was only sending out to 712 members, yet a total of 798 messages ended up being sent.
I am logging the messages that get sent out and I was able to tell that the first 86 members received the message twice. Here is the log (in the order the messages were sent)
No. Member Date
1. 163992 3/8/2012 12:28:13 PM
2. 163993 3/8/2012 12:28:13 PM
...
85. 164469 3/8/2012 12:28:37 PM
86. 163992 3/8/2012 12:28:44 PM
87. 163993 3/8/2012 12:28:44 PM
...
798. 167691 3/8/2012 12:32:36 PM
Each member should receive the newsletter once, however, as you can see member 163992 receives message #1 and #86; member 163993 received message #2 and #87; and so on.
The other thing to note is that there was a 7 second delay between sending message #85 and #86.
I have reviewed the code several times and ruled out pretty much all of the code as being the cause of it, except for possibly the ThreadPool.QueueUserWorkItem
.
This is the first time I work with ThreadPool, so I am not that familiar with it. Is it possible to have some sort of race-condition that is causing this behavior?
=== --- Code Sample --- ===
foreach (var recipient in recipientsToEmail)
{
_emailSender.SendMemberRegistrationActivationReminder(eventArgs.Newsletter, eventArgs.RecipientNotificationInfo, previewEmail: string.Empty);
}
public void SendMemberRegistrationActivationReminder(DomainObjects.Newsletters.Newsletter newsletter, DomainObjects.Members.MemberEmailNotificationInfo recipient, string previewEmail)
{
//Build message here .....
//Send the message
this.SendEmailAsync(fromAddress: _settings.WebmasterEmail,
toAddress: previewEmail.IsEmailFormat()
? previewEmail
: recipientNotificationInfo.Email,
subject: emailSubject,
body: completeMessageBody,
memberId: previewEmail.IsEmailFormat()
? null //if this is a preview message, do not mark it as being sent to this member
: (int?)recipientNotificationInfo.RecipientMemberPhotoInfo.Id,
newsletterId: newsletter.Id,
newsletterTypeId: newsletter.NewsletterTypeId,
utmCampaign: utmCampaign,
languageCode: recipientNotificationInfo.LanguageCode);
}
private void SendEmailAsync(string fromAddress, string toAddress, string subject, MultiPartMessageBody body, int? memberId, string utmCampaign, string languageCode, int? newsletterId = null, DomainObjects.Newsletters.NewsletterTypeEnum? newsletterTypeId = null)
{
var urlHelper = UrlHelper();
var viewOnlineUrlFormat = urlHelper.RouteUrl("UtilityEmailRead", new { msgid = "msgid", hash = "hash" });
ThreadPool.QueueUserWorkItem(state => SendEmail(fromAddress, toAddress, subject, body, memberId, newsletterId, newsletterTypeId, utmCampaign, viewOnlineUrlFormat, languageCode));
}
Upvotes: 8
Views: 1303
Reputation: 4336
Are you sure the query you are running to get the list of members to send the email to does not have duplicates in it? Are you joining to another table? What you could do is:
List<DomainObjects.Members.MemberEmailNotificationInfo> list = GetListFromDatabase();
list = list.Distinct().ToList();
Upvotes: 3
Reputation: 1280
In your code sample, we can't see where your logging takes place.
Maybe the mehod that sends the email erronously thought that something wrong occured then, the system tried again, which could result in an email sent twice.
Also, as written in other answers and comment, I would check again that I don't get duplicated entries in the list of recipients, and test it in a non-parallel context.
Upvotes: 1
Reputation: 26474
Having 800+ threads running on the server is not a good practice! Although you are using a ThreadPool, threads are being queued on the server and run whenever old threads return back to the pool and release the resource. This might take several minutes on the server and many situations like Race Conditions or Concurrencies might happen during that time. You could instead queue one work item, over one protected list:
lock (recipientsToEmail)
{
ThreadPool.QueueUserWorkItem(t =>
{
// enumerate recipientsToEmail and send email
});
}
Upvotes: 2
Reputation: 18654
One common cause of tasks getting performed twice in code where you queue the task to a background thread is faulty error handling. You might double-check your code to make sure that if there's an error that you don't always retry, regardless of the type of error (some errors warrant a retry; others don't).
Having said that, the code you've posted doesn't include enough information to definitively answer your question; there are many possibilities.
FWIW, are you aware that the SmtpClient
class has a SendAsync()
method, that doesn't require the use of a separate worker thread?
Upvotes: 1
Reputation: 7452
If this code:
foreach (var recipient in recipientsToEmail)
{
_emailSender.SendMemberRegistrationActivationReminder(eventArgs.Newsletter
,eventArgs.RecipientNotificationInfo, previewEmail: string.Empty);
}
matches what you are actually doing... you have an obvious bug. namely that you are doing a foreach but not using the returned value, so you will send the same email to eventArgs.RecipientNotificationInfo
for each entry in recipientsToEmail
.
Upvotes: 1
Reputation: 54359
Things to check (I'm assuming you have a way to mock the sending of emails):
SendEmail()
doing anything of significance? (I don't see your code for it)SendAsync()
method?For what it's worth, sending bulk email from your own site--even when it is completely legitimate--is not always worth the trouble. Spam blocking services are very aggressive and you don't want your domain to end up blacklisted. Third party services remove that risk, provide many tools, and also manage this part of the process for you.
Upvotes: 1