部分工作两次(ThreadPool.QueueUserWorkItem)

时间:2012-03-08 23:54:52

标签: c# asp.net threadpool

我创建了一个时事通讯系统,允许我指定哪些成员应该收到时事通讯。然后,我遍历符合条件的成员列表,对于每个成员,我生成一个个性化的消息并以异步方式向他们发送电子邮件。

当我发送电子邮件时,我正在使用ThreadPool.QueueUserWorkItem

出于某种原因,成员的一部分正在收到两次电子邮件。在我的最后一批中,我只发送给712名成员,但最终共发送了798封邮件。

我正在记录发出的消息,我能够告诉前86位成员收到消息两次。这是日志(按消息发送的顺序)

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

每个成员都应该收到一次通讯,但是,您可以看到成员163992收到消息#1和#86;成员163993收到消息#2和#87;等等。

另外需要注意的是,在发送#85和#86之间有7秒的延迟。

我已多次查看代码并排除了几乎所有代码的原因,除了可能是ThreadPool.QueueUserWorkItem

这是我第一次使用ThreadPool,所以我对它并不熟悉。是否有可能出现导致这种行为的某种竞争条件?

=== ---代码示例--- ===

    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));
    }

6 个答案:

答案 0 :(得分:3)

您确定要运行的查询以获取发送电子邮件的成员列表中没有重复项吗?你要加入另一张桌子吗?你能做的是:

List<DomainObjects.Members.MemberEmailNotificationInfo> list = GetListFromDatabase();
list = list.Distinct().ToList();

答案 1 :(得分:2)

在服务器上运行800多个线程不是一个好习惯! 虽然您使用的是ThreadPool,但是线程正在服务器上排队,并在旧线程返回池并释放资源时运行。这可能需要几分钟才能在服务器上运行,并且在此期间可能会发生竞争条件或合并等许多情况。 您可以在一个受保护的列表上排队一个工作项:

lock (recipientsToEmail)
{
    ThreadPool.QueueUserWorkItem(t =>
        {
            // enumerate recipientsToEmail and send email
        });
}

答案 2 :(得分:1)

要检查的事情(我假设你有办法嘲笑发送电子邮件):

  • 重复的电子邮件数量是否始终完全相同?如果增加/减少输入值的数量怎么办?它总是与重复的用户ID相同吗?
  • SendEmail()有什么意义吗? (我没有看到你的代码)
  • 您是否有理由不使用framework's SendAsync() method
  • 没有多线程,你会得到相同的行为吗?

对于它的价值,从您自己的网站发送批量电子邮件 - 即使它是完全合法的 - 并不总是值得的。垃圾邮件拦截服务非常具有侵略性,您不希望自己的域名最终被列入黑名单。第三方服务可以消除这种风险,提供许多工具,并为您管理这部分流程。

答案 3 :(得分:1)

如果这段代码:

foreach (var recipient in recipientsToEmail)
{
    _emailSender.SendMemberRegistrationActivationReminder(eventArgs.Newsletter
    ,eventArgs.RecipientNotificationInfo, previewEmail: string.Empty);
}

匹配你实际做的......你有一个明显的错误。即您正在使用foreach但未使用返回的值,因此您将eventArgs.RecipientNotificationInfo中的每个条目向recipientsToEmail发送相同的电子邮件。

答案 4 :(得分:1)

在将任务排入后台线程的代码中执行两次任务的一个常见原因是错误处理错误。您可以仔细检查您的代码,以确保如果出现错误,您总是重试,无论错误类型如何(某些错误都需要重试;其他错误则不然)。< / p>

话虽如此,您发布的代码中没有足够的信息来明确回答您的问题;有很多种可能性。

FWIW,您是否知道SmtpClient类有一个SendAsync()方法,不需要使用单独的工作线程?

答案 5 :(得分:1)

在您的代码示例中,我们无法查看您的日志记录发生的位置。

也许发送电子邮件的方法错误地认为发生了错误,系统再次尝试,这可能导致发送两次电子邮件。

另外,正如其他答案和评论中所写,我会再次检查我没有在收件人列表中获得重复的条目,并在非并行的上下文中进行测试。