部分工作已完成两次(ThreadPool.QueueUserWorkItem)

本文关键字:两次 ThreadPool QueueUserWorkItem 工作 已完成 | 更新日期: 2023-09-27 18:27:16

我创建了一个时事通讯系统,允许我指定哪些成员应该接收时事通讯。然后,我循环浏览符合标准的成员列表,为每个成员生成一条个性化消息,并异步发送电子邮件。

当我发送电子邮件时,我使用的是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));
    }

部分工作已完成两次(ThreadPool.QueueUserWorkItem)

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

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

在服务器上运行800多个线程不是一个好做法!尽管您使用的是ThreadPool,但线程会在服务器上排队,并在旧线程返回池并释放资源时运行。这可能需要在服务器上花费几分钟时间,并且在这段时间内可能会发生许多情况,如"竞争条件"或"并发"。相反,您可以在一个受保护的列表上对一个工作项进行排队:

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

需要检查的内容(我假设你有一种方法可以模拟电子邮件的发送):

  • 重复电子邮件的数量总是完全相同吗?如果您增加/减少输入值的数量会怎样?是否总是相同的用户ID重复
  • SendEmail()做了什么有意义的事情吗?(我看不到你的代码)
  • 您不使用框架的SendAsync()方法是有原因的吗
  • 在没有多线程的情况下,你会得到同样的行为吗

就其价值而言,从自己的网站发送大量电子邮件——即使它是完全合法的——也不总是值得麻烦的。垃圾邮件屏蔽服务非常激进,你不希望你的域名最终被列入黑名单。第三方服务消除了这种风险,提供了许多工具,还为您管理了流程的这一部分。

如果此代码:

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

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

在将任务排队到后台线程的代码中,任务被执行两次的一个常见原因是错误处理。您可能会仔细检查您的代码,以确保如果出现错误,无论错误类型如何,您都不会总是重试(有些错误需要重试,有些则不需要)。

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

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

在您的代码示例中,我们看不到日志记录发生在哪里。

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

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