2012-03-08 15 views
9

He creado un sistema de boletín de noticias que me permite especificar qué miembros deberían recibir el boletín. Luego recorro la lista de miembros que cumplen los criterios y, para cada miembro, genero un mensaje personalizado y les envío el correo electrónico de manera asíncrona.Se está realizando un trabajo parcial dos veces (ThreadPool.QueueUserWorkItem)

Cuando envío el correo electrónico, estoy usando ThreadPool.QueueUserWorkItem.

Por alguna razón, un subconjunto de los miembros reciben el correo electrónico dos veces. En mi último lote, solo estaba enviando a 712 miembros, pero un total de 798 mensajes terminaron siendo enviados.

Estoy registrando los mensajes que se envían y pude decir que los primeros 86 miembros recibieron el mensaje dos veces. Aquí está el registro (en el orden en que se envían los mensajes)

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 

Cada miembro debe recibir el boletín informativo de una vez, sin embargo, como se puede ver miembro de 163,992 recibe el mensaje # 1 y # 86; el miembro 163993 recibió el mensaje n. ° 2 y n. ° 87; y así.

La otra cosa a tener en cuenta es que hubo un retraso de 7 segundos entre el envío del mensaje # 85 y # 86.

He revisado el código varias veces y he descartado casi todo el código como la causa, excepto posiblemente el ThreadPool.QueueUserWorkItem.

Esta es la primera vez que trabajo con ThreadPool, así que no estoy tan familiarizado con él. ¿Es posible tener algún tipo de condición racial que está causando este comportamiento?

=== --- --- Código de ejemplo ===

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

Parece condición de carrera para mí - Si utiliza una cola Cómo se elimina elemento de la cola antes de llamar ThreadPool.QueueUserWorkItem()? ¿Podemos ver tu código? – alexm

+0

No estoy usando ningún otro tipo de cola. Básicamente: recorra la lista de miembros que cumplen con los requisitos, genere correos electrónicos para miembros, agregue la llamada al método que realmente envía correos electrónicos a ThreadPool. –

+0

Para evitar duplicados mantenga lista de usuarios que tienen un correo electrónico pendiente – alexm

Respuesta

2

Tener más de 800 subprocesos que se ejecutan en el servidor no es una buena práctica! Aunque está utilizando un ThreadPool, los subprocesos se están poniendo en cola en el servidor y se ejecutan cada vez que los subprocesos antiguos vuelven al grupo y liberan el recurso. Esto puede llevar varios minutos en el servidor y muchas situaciones como Condiciones de carrera o Concurrencia pueden ocurrir durante ese tiempo. Se podría programar de una vez elemento de trabajo, más de una lista de especies protegidas:

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

Cuando envié el boletín informativo, estaba aprovechando el sistema de correo electrónico que ya estaba en uso cuando enviaba un mensaje a la vez. Realmente no pensé en eso, pero sí, tener más de 800 hilos parece ser el enfoque equivocado. Repasé mi código para que haga girar un nuevo hilo y que procese los mensajes del boletín. –

1

cosas a comprobar (estoy suponiendo que tiene una manera de burlar el envío de mensajes de correo electrónico):

  • ¿El número de correos electrónicos duplicados es siempre el mismo? ¿Qué sucede si aumenta/disminuye el número de valores de entrada? ¿Siempre son los mismos ID de usuario los que están duplicados?
  • ¿Está haciendo algo significativo el SendEmail()? (No veo el código correspondiente)
  • ¿Hay algún motivo por el que no esté utilizando el framework's SendAsync() method?
  • ¿Obtiene el mismo comportamiento sin multihilo?

Por lo que vale, no siempre merece la pena enviar correos electrónicos a granel desde su propio sitio, incluso cuando es completamente legítimo. Los servicios de bloqueo de correo no deseado son muy agresivos y no desea que su dominio termine en una lista negra. Los servicios de terceros eliminan ese riesgo, proporcionan muchas herramientas y también administran esta parte del proceso por usted.

+0

Tim, en realidad SendEmail registra el contenido del mensaje para que luego pueda "Ver en línea". No tenía idea de que SmtpClient tenía un método SendAsync, cambiaré a eso. Además, en realidad estoy usando SendGrid para manejar la entrega del correo electrónico. Solo estoy generando el mensaje en mi sitio y usando su servidor SMTP. –

+0

"¿Obtienes el mismo comportamiento sin multihilo?" sería el primer punto para mí, además de verificar los itemps duplicados en la lista. – remio

3

¿Estás seguro de que la consulta que estás ejecutando para obtener la lista de miembros a los que enviar el correo electrónico no tiene duplicados? ¿Te estás uniendo a otra mesa? Lo que podría hacer es:

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

Si este código:

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

coincide con lo que están haciendo en realidad ... usted tiene un error evidente.a saber, que está haciendo un foreach pero no está utilizando el valor devuelto, por lo que enviará el mismo correo electrónico al eventArgs.RecipientNotificationInfo para cada entrada en recipientsToEmail.

1

Una causa común de tareas consiguiendo realizó dos veces en el código en el que la tarea en cola a un subproceso de fondo es la gestión de errores defectuosa. Puede verificar dos veces su código para asegurarse de que si hay un error que no se siempre reintentar, independientemente del tipo de error (algunos errores justifican un reintento, otros no).

Una vez dicho esto, el código que has publicado no incluye suficiente información para responder definitivamente a su pregunta; hay muchas posibilidades

FWIW, ¿sabe que la clase SmtpClient tiene un método SendAsync(), que no requiere el uso de un subproceso independiente?

1

En el ejemplo de código, que no podemos ver dónde está su registro se lleva a cabo.

Tal vez el mehod que envía el correo electrónico erronously pensó que algo malo ocurrió entonces, el sistema ha intentado de nuevo, lo que podría resultar en un correo electrónico enviado dos veces.

Además, como está escrito en otras respuestas y comentarios, me gustaría comprobar una vez más que no consigo entradas duplicadas en la lista de destinatarios, y probarlo en un contexto no paralela.

Cuestiones relacionadas