Versenden von E-Mails in separaten threads mit QueueUserWorkItem
Ich habe eine Konsole-Anwendung, die sendet maßgeschneiderten E-Mails (mit Anlagen) an verschiedene Empfänger und ich möchte Ihnen gleichzeitig. Ich brauche die Erstellung von separaten SmtpClients um dies zu erreichen, so bin ich mit QueueUserWorkItem zu erstellen, die E-Mails und senden Sie Sie in separate threads.
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());
Habe ich bemerkt (nur zeitweise), dass manchmal nicht alle emails kommen in die bestimmte Postfächer mit dem obigen code. Ich hätte gedacht, dass mit Send
über SendAsync
würde bedeuten, die E-Mail wurde geschickt von der Anwendung. Doch indem Sie die folgende Zeile des Codes nach der WaitHandle.WaitAll
Linie:
System.Threading.Thread.Sleep(5000);
Scheint zu funktionieren. Mein denken ist, aus welchem Grund auch immer, einige E-Mails noch nicht gesendet wurde (auch nach der Send
Methode ausgeführt wurde). Geben Sie die zusätzlichen 5 Sekunden zu geben scheint die Anwendung genug Zeit, um fertig zu stellen.
Ist dies vielleicht ein Problem mit der Art und Weise, warte ich auf die E-Mails zu senden? Oder ist das ein Problem mit dem eigentlichen "Send" - Methode? Hat die E-Mail definitiv versendet wurden, von der app einmal passieren wir diese Linie?
Irgendwelche Gedanken die Idee zu diesem wäre toll, kann nicht ganz scheinen, um meine finger auf die eigentliche Ursache.
Update
Verlangt wie 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();
}
- Können Sie erstellen, die eine kurze, aber komplette, Programm das stellt das problem?
- Ich werde versuchen die vorgeschlagenen Lösungen dann posten, wenn noch nichts.
- Können Sie Ihre SMTP-code?
- Warum kannst du nicht verwenden
SendAsync
und einfach Prozess der Abschluss-Ereignisse, so dass Sie wissen, ob alle E-Mails die gesendet wurden? - Ich glaube, du verpasst einige der code. Ich sehe keinen Anruf zu senden.
- Ich möchte zum senden der Benachrichtigungen so schnell wie möglich und Sie können ausgelöst werden, ziemlich viel 1 nach dem anderen, man darf nur senden, 1 asychronously auf jeden 1 mal, das war meine ursprüngliche Idee.
- Sie nicht ernsthaft erwarten, dass ich post meine vollständigen code der Anwendung haben Sie? Sicherlich wird der code geschrieben in meinem Fragen & Antworten ist genug für Sie zu gehen?
- Nicht die volle Anwendung, nur senden E-Mail-Prozess, wenn es möglich ist wie diese stackoverflow.com/a/1687178/206730 ich möchte vergleichen, Beispiele, finden, klarer, eleganter code
Du musst angemeldet sein, um einen Kommentar abzugeben.
Eines der Dinge, die nervt mich über Ihren code, den Sie aufrufen
events.Add
innerhalb der thread-Methode. DieDictionary<TKey, TValue>
Klasse ist nicht thread-sicher; dieser code sollte nicht in den thread.Update: ich denke ChaosPandion geschrieben, eine gute Umsetzung, aber ich würde es noch einfacher, machen Sie es so, kann nichts möglicherweise schief gehen in Sachen thread-Sicherheit:
Habe ich beseitigt, das Wörterbuch komplett hier, und alle
AutoResetEvent
Instanzen erstellt werden, in dem gleichen thread, später führt eineWaitAll
. Wenn dieser code nicht funktioniert, dann muss es ein problem mit der e-mail selbst; entweder ist der server ablegen von Nachrichten (wie viele sind Sie das senden?) oder Sie versuchen zu teilen, etwas, was nicht thread-safe ist zwischenAlert
Instanzen (evtl ein singleton oder etwas erklärt statisch).WaitAll
geschieht, bevor alle threads haben, hatte sogar eine chance zu geben, Ihre Veranstaltung zu dem Wörterbuch. Das ist, warum es wichtig ist, initialisieren Sie die Liste in dem selben thread, dass Sie warten.delegate
zuo =>
.Werden Sie wahrscheinlich wollen, dies zu tun...
ThreadPool.QueueUserWorkItem((state) =>{ events[(Guid)state].Set();}, id);
Worked
oderDidn't Work
.AutoResetEvent
statt? Das würde zu beseitigen, die möglicherweise nicht Thread-sichere Wörterbuch nachschlagen. Oder noch besser, don ' T-pass-Zustand; speichern derAutoResetEvent
in eine lokale variable, bevor Sie es dem Wörterbuch, und legen Sie einfachevent.Set()
im delegieren. Je mehr ich schaue, desto mehr der Wörterbuch scheint unnötig, man könnte dies genauso gut mit einemList
.Der Grund, warum es nicht funktioniert ist, dass wenn er trifft Veranstaltungen.Werte.ToArray() nicht alle Delegierten in der Warteschlange ausgeführt haben und daher nicht alle AutoResetEvent-Instanzen wurden Hinzugefügt, um das Wörterbuch.
Beim Aufruf ToArray() die Werte, auf die Eigenschaft abrufen, erhalten Sie nur diejenigen Instanzen, die bereits Hinzugefügt!
Bedeutet dies, werden Sie warten, nur ein paar E-Mails gesendet werden, synchron, bevor der thread blockiert wird fortgesetzt. Der rest der E-Mails, die noch verarbeitet werden, indem die ThreadPool-threads.
Gibt es einen besseren Weg, aber
dies ist ein hackerscheint es sinnlos, etwas zu tun, asynchron, wenn Sie möchten, um zu blockieren den aufrufenden thread am Ende...Okay, unter Berücksichtigung der folgenden Anforderungen:
Ich würde erstellen Sie die folgende Anwendung:
Laden Sie die E-Mails aus einer text-Datei (Datei.ReadAllLines). Erstellen Sie als Nächstes 2* (Anzahl der CPU-Kerne) Threads. Bestimmen Sie die Anzahl der Zeilen, die verarbeitet werden pro thread; D. H., dividieren Sie die Anzahl der Zeilen (addy pro Zeile) durch die Anzahl der threads, aufgerundet. Als Nächstes legen Sie jeden Faden die Aufgabe, durch seine Liste von Adressen (verwenden Sie Skip(int).Nehmen, (int) aufteilen der Zeilen) und Send()ing jedem E-Mail-synchron. Jeder thread erstellen würde und die eigene SmtpClient. Wie jeder Thread abgeschlossen ist, erhöht ein int in einem freigegebenen Verzeichnis gespeichert. Wenn das int ist gleich der Anzahl der threads, ich kenne alle threads abgeschlossen haben. Der Haupt-Konsole-thread wird ständig prüfen, ob diese Zahl für die Gleichheit und Sleep() für eine bestimmte Länge der Zeit, bevor es die Kontrolle wieder.
Das klingt ein bisschen notdürftigem, aber es funktioniert. Sie können zwicken die Anzahl der threads, um den besten Durchsatz für eine einzelne Maschine, und dann extrapolieren, dass, um zu bestimmen, die richtige Anzahl von threads. Es gibt sicherlich elegantere Wege, die Sperrung der Konsole thread bis zur vollständigen, aber keines so einfach.
Send()
möglicherweise eine relativ lange Zeit zum ausführen, aber wenn der mail-server akzeptiert 10 verbindungen gleichzeitig dann die parallele version wird die Oberfläche sehr viel schneller.alert.Send()
blockiert, bis die gesendet werden (Sie müssen erstellen Sie ein demo-Projekt, um dies zu testen), dann schicken Sie Sie synchron. Wenn Sie verhindern möchten, dass die Anwendung heruntergefahren aus, stoppen Sie aus immer geschickt, einen neuen Thread erstellen und verwenden, um Ihre alerts (d.h., nicht mit einem "hintergrund" - thread, siehe hier: msdn.microsoft.com/en-us/library/h339syd0.aspx )Send
- Methode ist wahrscheinlich die Blöcke für eine Sekunde oder zwei, aber die meiste Zeit im Leerlauf ist, es ist nur das senden von Daten über den Draht und/oder das warten auf eine Antwort vom mail-server. Senden Sie parallel dazu verbessern den Durchsatz (nehme ich an).Ich hatte ein ähnliches problem (mit SmtpClient aus einem thread und die E-Mails kommen nur sporadisch).
Zunächst die Klasse, die sendet E-Mails erstellt eine einzelne Instanz der SmtpClient. Das problem wurde gelöst, indem der code zum erstellen einer neuen Instanz der SmtpClient jedesmal, wenn eine E-Mail geschickt werden muss und Entsorgung der SmtpClient (mit einer using-Anweisung).