2010-01-08 8 views
6

Ich habe eine Konsolenanwendung, die benutzerdefinierte E-Mails (mit Anhängen) an verschiedene Empfänger sendet und ich möchte sie gleichzeitig senden. Ich muss separate SmtpClients erstellen, um dies zu erreichen, also verwende ich QueueUserWorkItem, um die E-Mails zu erstellen und sie in separaten Threads zu senden.Senden von E-Mails in separaten Threads mit QueueUserWorkItem

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

ich bemerkt habe (mit Unterbrechungen), dass manchmal nicht alle E-Mails in den spezifischen Postfächer mit dem obigen Code gelangen. Ich hätte gedacht, dass die Verwendung von Send über SendAsync bedeutet, dass die E-Mail definitiv von der Anwendung gesendet wurde. Allerdings Hinzufügen der folgenden Codezeile nach der WaitHandle.WaitAll Linie:

System.Threading.Thread.Sleep(5000); 

scheint zu funktionieren. Ich denke, aus irgendeinem Grund wurden einige E-Mails immer noch nicht gesendet (auch nachdem die Methode Send ausgeführt wurde). Wenn man diese zusätzlichen 5 Sekunden gibt, scheint die Anwendung genug Zeit zum Beenden zu geben.

Ist das vielleicht ein Problem mit der Art, wie ich auf die E-Mails warten soll? Oder ist das ein Problem mit der tatsächlichen Send-Methode? Wurde die E-Mail definitiv von der App gesendet, nachdem wir diese Zeile passiert haben?

Irgendeine Gedanken Idee ist auf diesem würde groß sein, kann nicht ganz scheinen, meinen Finger auf die eigentliche Ursache zu legen.

aktualisieren

Wie gewünscht hier ist der 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(); 
} 
+0

Können Sie ein kurzes, aber vollständiges Programm erstellen, das das Problem aufweist? –

+0

@Lasse Ich werde versuchen die vorgeschlagenen Lösungen dann zu posten, wenn noch nichts. – James

+0

Können Sie Ihren SMTP-Code posten? – ChaosPandion

Antwort

4

Eines der Dinge, die über abgehört hat mich Ihr Code ist, dass Sie innerhalb der Thread-Methode events.Add aufrufen. Die Dictionary<TKey, TValue> Klasse ist nicht Thread-sicher; Dieser Code sollte nicht innerhalb des Threads sein.

Update: Ich denke ChaosPandion eine gute Implementierung geschrieben, aber ich würde es noch einfacher machen, machen es so nichts kann möglicherweise schief gehen in Bezug auf die Thread-Sicherheit:

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‘ Das Wörterbuch wurde hier komplett eliminiert, und alle AutoResetEvent Instanzen werden in demselben Thread erstellt, der später eine WaitAll ausführt. Wenn dieser Code nicht funktioniert, muss es ein Problem mit der E-Mail selbst sein; Entweder gibt der Server Nachrichten aus (wie viele senden Sie?) oder Sie versuchen etwas nicht threadsicher zwischen Alert Instanzen zu teilen (möglicherweise ein Singleton oder etwas, das statisch deklariert wird).

+0

Aber sicher wird jeder Thread in Ordnung signalisiert, sonst würde meine Anwendung für immer warten? – James

+0

Nicht, wenn das 'WaitAll' passiert, bevor alle Threads überhaupt eine Chance hatten, ihr Ereignis dem Wörterbuch hinzuzufügen. Deshalb ist es wichtig, die Liste in demselben Thread zu initialisieren, in dem Sie warten. – Aaronaught

+0

Ah, sehr guter Punkt! Ich denke aber, dass wir den Nagel auf den Kopf getroffen haben. – James

2

Sie wahrscheinlich dies tun wollen ...

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

oder 'ThreadPool.QueueUserWorkItem ((Zustand) => {events [(Guid) state].Set();}, id); ' –

+0

Ich habe das getan, also muss ich nicht fragen, ob er .NET 3.5 hat. – ChaosPandion

+0

Für den Rekord habe ich 3,5, nur versuchen Sie Ihre Lösung im Mo wird zurück zu euch kommen! – James

2

Der Grund, warum es nicht funktioniert, wenn er events.Values.ToArray trifft() nicht alle der Warteschlange Delegierten ausgeführt haben und deshalb nicht alle Instanzen haben Autoreset dem Wörterbuch hinzugefügt.

Wenn Sie ToArray() in der Values-Eigenschaft aufrufen, werden nur die ARE-Instanzen hinzugefügt!

Dies bedeutet, dass Sie warten, bis nur noch wenige der E-Mails synchron gesendet werden, bevor der blockierte Thread fortgesetzt wird. Der Rest der E-Mails muss noch von den ThreadPool-Threads verarbeitet werden.

Es gibt einen besseren Weg, aber dies ein Hack ist es sinnlos scheint asynchron, etwas zu tun, wenn Sie den aufrufenden Thread am Ende blockieren möchten ...

var doneLol = new AutoResetEvent(); 

ThreadPool.QueueUserWorkItem(
delegate 
{ 
    foreach (...) 
    { 
    var id = Guid.NewGuid(); 
    var alert = HurrDurr.CreateAlert(...); 
    alert.Send(); 
    } 
    doneLol.Set(); 
}); 

doneLol.WaitOne(); 

Okay, die folgenden Anforderungen unter Berücksichtigung

  1. Console App
  2. Viele E-Mails
  3. so schnell wie möglich Sent

ich die folgende Anwendung erstellen würde:

Legen Sie die E-Mails aus einer Textdatei (File.ReadAllLines). Als nächstes erstellen Sie 2 * (# von CPU-Kernen) Threads. Ermitteln Sie die Anzahl der Zeilen pro Thread; d. h. dividiere die Anzahl der Zeilen (addy pro Zeile) durch die Anzahl der Threads, die aufgerundet werden. Als nächstes setze jeden Thread auf die Aufgabe, seine Adressenliste durchzugehen (verwende Skip (int) .Take (int), um die Zeilen aufzuteilen) und sende() jede E-Mail synchron. Jeder Thread würde einen eigenen SmtpClient erstellen und verwenden. Wenn jeder Thread abgeschlossen ist, erhöht er einen Int, der an einem freigegebenen Speicherort gespeichert ist. Wenn dieses int gleich der Anzahl der Threads ist, weiß ich, dass alle Threads abgeschlossen sind. Der Hauptkonsolenthread überprüft diese Nummer kontinuierlich auf Gleichheit und Sleep() für eine festgelegte Zeitspanne, bevor er erneut überprüft wird.

Das klingt ein bisschen kludgy, aber es wird funktionieren. Sie können die Anzahl der Threads optimieren, um den besten Durchsatz für eine einzelne Maschine zu erhalten, und dann daraus extrapolieren, um die richtige Anzahl an Threads zu ermitteln. Es gibt definitiv elegantere Möglichkeiten, den Konsolen-Thread bis zur Fertigstellung zu blockieren, aber keines ist so einfach.

+0

Ich denke, Sie könnten Recht haben. Ich werde es versuchen. – James

+0

Natürlich verwendet dies nur einen Thread für alle Alarme ... sie werden immer noch sequentiell anstatt parallel gesendet. Wenn es das Ziel ist, es nur im Hintergrund zu tun, dann wird das gut funktionieren, aber wenn er versucht, sie alle parallel zu senden, dann macht das nicht genau dasselbe. – Aaronaught

+0

Ich weiß, es ist ungefähr sinnlos. Warum asynchron wenn du blocken willst? – Will

0

Ich hatte ein ähnliches Problem (mit SmtpClient aus einem Thread und die E-Mails kommen nur zeitweise).

Ursprünglich erstellte die Klasse, die E-Mails sendet, eine einzelne Instanz von SmtpClient. Das Problem wurde gelöst, indem der Code geändert wurde, um eine neue Instanz von SmtpClient zu erstellen, immer wenn eine E-Mail gesendet werden muss und den SmtpClient entsorgend (mit einer using-Anweisung).

SmtpClient smtpClient = InitSMTPClient(); 
using (smtpClient) 
{ 
    MailMessage mail = new MailMessage(); 
    ... 
    smtpClient.Send(mail); 
} 
Verwandte Themen