Multithreading‑Unterstützung und Connection‑Pool in Mail‑Clients

Mail‑Clients wie ImapClient, Pop3Client, und SmtpClient kann in einer Multithread‑Umgebung verwendet werden. Ein Client kann eine oder mehrere Verbindungen zu einem Server halten. Zur Verwaltung des Verbindungsbestands innerhalb eines Clients wird ein Connection‑Pool verwendet. Die Anzahl der gleichzeitig erstellbaren und nutzbaren Verbindungen ist begrenzt durch den CredentialsByHostClient.MaxConnectionsPerServer Eigenschaft. Diese Eigenschaft kann auf 1 oder einen höheren Wert gesetzt werden. Standardmäßig ist sie 10.

Für jede Verbindung wird eine Befehlswarteschlange implementiert, um Multithreading‑Vorgänge zu unterstützen. Befehle implementieren die einfachsten im Protokoll definierten Vorgänge, wie Noop, Authenticate, und so weiter. Ein Benutzer kann mehr Befehle starten, als verfügbare Verbindungen existieren, aber sie werden erst ausgeführt, wenn der Client in der Lage ist, eine Verbindung für den Vorgang zu erstellen.

Wie sich Mail‑Clients in einer Multithread‑Umgebung verhalten

E‑Mail‑Clients haben das folgende Verhalten:

  1. Wenn MaxConnectionsPerServer = 1, erstellt der Client eine Verbindung und führt Authentifizierung und Autorisierung durch. Diese Verbindung bleibt im Arbeitszustand, bis der Client disposet wird. Alle Vorgänge aus verschiedenen Threads werden in eine Befehlswarteschlange der Hauptverbindung geleitet.

  2. Wenn MaxConnectionsPerServer > 1, erstellt der Client die benötigte Anzahl an Verbindungen und führt Authentifizierung und Autorisierung für jede Verbindung durch. Eine Verbindung wird als Hauptverbindung reserviert. Diese Verbindung bleibt im Arbeitszustand, bis der Client disposet wird. Alle anderen Verbindungen werden bei Bedarf erstellt und disposet. Die maximale Anzahl solcher Verbindungen wird durch den MaxConnectionsPerServer Eigenschaft. Zum Beispiel, wenn MaxConnectionsPerServer = 2, dann wird eine Verbindung als Hauptverbindung reserviert und eine zweite Verbindung als zusätzliche für Vorgänge in anderen Threads verwendet. Dementsprechend, wenn MaxConnectionsPerServer = 3, dann wird die erste Verbindung als Hauptverbindung reserviert und zwei weitere Verbindungen als zusätzliche für Vorgänge in anderen Threads verwendet. Wenn eine Verbindungsanfrage von einem neuen Thread kommt und alle Verbindungen bereits belegt sind, wartet der Client, bis die Anzahl der genutzten Verbindungen sinkt. Dies ist ein sehr wichtiger Punkt, der erklärt, warum das korrekte Disposen von Verbindungen so wichtig ist.

Beispiele für die Verwendung von Mail‑Clients in einer Multithread‑Umgebung

Ein Benutzer kann Vorgänge in verschiedenen Threads auf mehrere Arten ausführen. Sie lassen sich in zwei Typen einteilen.

Verwendung asynchroner (Begin/End)‑Methoden

Ein Benutzer verwendet die asynchronen (Begin/End)‑Methoden, die im Client definiert sind. In diesem Fall startet der Mail‑Client bei Bedarf neue Threads. In dem Client wird eine Aufgaben‑Warteschlange implementiert (nicht zu verwechseln mit der Befehlswarteschlange in der Verbindung). Eine Aufgabe kann ausgeführt werden, wenn eine Verbindung verfügbar ist. Sobald die Anzahl der genutzten Verbindungen unter den Grenzwert fällt, erstellt der Client eine neue Verbindung, erzeugt einen Thread für die aktuelle Aufgabe und führt diese Aufgabe aus. Ein Beispiel für die Verwendung asynchroner Vorgänge:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.Host = "domain.com";
client.Username = "username";
client.Password = "password";
client.SelectFolder("InBox");

ImapMessageInfoCollection messages = client.ListMessages();
IAsyncResult res1 = client.BeginFetchMessage(messages[0].UniqueId);
IAsyncResult res2 = client.BeginFetchMessage(messages[1].UniqueId);
MailMessage msg1 = client.EndFetchMessage(res1);
MailMessage msg2 = client.EndFetchMessage(res2);

Verwendung von benutzerdefinierten Threads

Ein Benutzer kann Threads mit Objekten wie Thread, ThreadPool, Task, oder andere Objekte, die hierfür vorgesehen sind. Ein Benutzer kann auch Threads nutzen, die in Drittanbieter‑Code erstellt wurden. In diesem Fall hat der Client zwei Verhaltensmodelle.

a. Wenn der Benutzer keine zusätzlichen Verbindungen für Vorgänge im Thread erstellt hat, werden alle Vorgänge dieses Threads an die Befehlswarteschlange der Hauptverbindung gesendet. Das folgende Beispiel zeigt Vorgänge in einem zusätzlichen Thread, ohne eine neue Verbindung zu erstellen – alle Transaktionen erfolgen über die Hauptverbindung:

List<MailMessage> List = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    client.SelectFolder("folderName");
    ImapMessageInfoCollection messageInfoCol = client.ListMessages();
    foreach (ImapMessageInfo messageInfo in messageInfoCol)
    {
        List.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

b. Wenn der Benutzer eine Methode ausführt, um eine neue Verbindung für einen zusätzlichen Thread zu erstellen, wird dieser Thread blockiert, bis sich der Kontingentwert für neue Verbindungen ändert, sodass eine neue Verbindung erlaubt ist. Dann wird die neue Verbindung erstellt. Diese Verbindung wird als Standardverbindung für alle Vorgänge in diesem Thread gesetzt. Nachdem alle Vorgänge in diesem Thread abgeschlossen sind, muss die Verbindung disposet werden. Zum Erstellen neuer Verbindungen verwenden Sie den CredentialsByHostClient.CreateConnection Methode. Diese Methode gibt ein Objekt zurück, das das IDisposable Schnittstelle. Um die Verbindung freizugeben, die Dispose Methode muss aufgerufen werden. Das Erstellen und Disposen einer Verbindung muss innerhalb des Threads erfolgen, in dem die Mail‑Operationen ausgeführt werden. Der Versuch, in dem Thread, in dem der Mail‑Client erstellt wurde, eine neue Verbindung zu erstellen, führt zu einem Fehler, da dieser Thread zu diesem Zeitpunkt nicht zum Erstellen einer neuen Verbindung verwendet werden kann. Das Erstellen einer neuen Verbindung ist außerdem nicht möglich, wenn MaxConnectionsPerServer = 1. Ein Code‑Beispiel zum Erstellen einer neuen Verbindung in einem zusätzlichen Thread:

List<MailMessage> List1 = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    using (IDisposable connection = client.CreateConnection())
    {
        client.SelectFolder("FolderName");
        ImapMessageInfoCollection messageInfoCol = client.ListMessages();
        foreach (ImapMessageInfo messageInfo in messageInfoCol)
            List1.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

Connection‑Pool

Ab Version Aspose.Email 19.3 wurde der Connection‑Pool überarbeitet. Der EmailClient Klasse wurde eingeführt, die schließlich die CredentialsByHostClient Klasse. Die EmailClient Klasse stellt ein ConnectionAsgmtMode Eigenschaft, die den Modus der Verbindungszuweisung in einer Mehrthread‑Umgebung definiert. EmailClient.ConnectionAsgmtMode wird festgelegt mit ConnectionAsgmtType Enumeration.

Verbindungstypen

Es gibt drei Verbindungstypen:

  • Die Hauptverbindung. Diese Verbindung wird zusammen mit dem Mail‑Client erstellt und disposed. Sie kann nicht manuell erstellt oder disposed werden.
  • Standardverbindung. Ein Benutzer kann Standardverbindungen für Threads mit dem CreateConnection Methode erstellt werden. Existiert eine Standardverbindung, verwenden alle Methoden des E‑Mail‑Clients, die in einem Thread ausgeführt werden, implizit diese Verbindung. Pro Thread kann nur eine Standardverbindung existieren. Sie kann manuell oder automatisch erstellt werden, abhängig von dem EmailClient.ConnectionAsgmtMode Eigenschaft. Diese Verbindungen können manuell mit dem EmailClient.CreateConnection(createAsDefaultConnection = true) Methode erstellt werden. Wenn keine Standardverbindung verwendet wird (abhängig vom Verbindungszuweisungsmodus), wird stattdessen implizit die Hauptverbindung verwendet.
  • Unabhängige Verbindungen. Das sind Verbindungen, die nicht an Threads gebunden sind. Sie können manuell erstellt werden und müssen explizit als Methodenparameter verwendet werden. Diese Verbindungen können manuell mit dem EmailClient.CreateConnection() Methode oder die EmailClient.CreateConnection(createAsDefaultConnection = false) Methode.

Verbindung‑Zuweisungs‑Typen

Um die EmailClient.ConnectionAsgmtMode Eigenschaft, die ConnectionAsgmtType Aufzählung verwendet werden. Die bereitgestellten Zuweisungstypen sind unten aufgeführt.

  • ConnectionAsgmtType.UseMainOrDefault Dieser Modus wird standardmäßig in E‑Mail‑Clients verwendet. Der E‑Mail‑Client nutzt die Hauptverbindung für alle Vorgänge aus mehreren Threads, wenn keine Standardverbindung erstellt wurde oder wenn keine Verbindung explizit als Methodenparameter übergeben wurde. Die Hauptverbindung wird gleichzeitig mit dem E‑Mail‑Client erstellt. Der Benutzer kann Standardverbindungen für Threads mit dem CreateConnection Methode verwendet. Wenn für einen Thread eine Standardverbindung erstellt wird, wird sie implizit für alle Methoden des E‑Mail‑Clients verwendet, die in diesem Thread aufgerufen werden. Wenn für einen Thread keine Standardverbindung erstellt wird, wird die Hauptverbindung für alle Methoden in diesem Thread verwendet. Der Benutzer kann zudem Verbindungen erstellen, die nicht an Threads gebunden sind (keine Standardverbindungen), mit dem CreateConnection Methode. Um andere Verbindungen (nicht Haupt‑ und nicht Standardverbindungen) zu nutzen, muss der Benutzer die Verbindung explizit als Parameter der Methode übergeben. Der Benutzer kann zudem beliebig viele Verbindungen erstellen. Pro Thread kann nur eine Standardverbindung existieren. Bitte beachten Sie, dass Standardverbindungen korrekt funktionieren, wenn der Benutzer Thread Objekte für Multitasking‑Programmierung. Wenn der Benutzer einen Connection‑Pool oder Task Objekte für Multitasking, dieser Modus kann zu falschem Verhalten führen. Um das Problem zu vermeiden, muss der Benutzer die Standardverbindung (falls verwendet) am Ende der Codeausführung manuell disposen.

  • ConnectionAsgmtType.UseMain Der E‑Mail‑Client verwendet die Hauptverbindung für alle Vorgänge aus mehreren Threads. Die Hauptverbindung wird gleichzeitig mit dem E‑Mail‑Client erstellt. Der Benutzer kann keine Standardverbindungen erstellen, aber Verbindungen, die nicht an Threads gebunden sind, mit dem CreateConnection Methode erstellen. Um andere Verbindungen zu nutzen, muss der Benutzer sie explizit als Methodenparameter übergeben.

  • ConnectionAsgmtType.UseDefault Der E‑Mail‑Client verwendet implizit nur Standardverbindungen für alle Vorgänge aus mehreren Threads. Die Hauptverbindung wird in diesem Modus nicht verwendet. Wenn für einen Thread keine Standardverbindung erstellt wurde (bei der ersten Aufruf einer E‑Mail‑Client‑Methode), erstellt der E‑Mail‑Client implizit eine Standardverbindung für den Thread, bevor die erste Operation ausgeführt wird. Der Benutzer kann keine Standardverbindungen für Threads mit dem CreateConnection Methode verwendet, weil sie automatisch erstellt werden. Der Benutzer kann auch Verbindungen erstellen, die nicht an Threads gebunden sind, mit dem CreateConnection Methode verwendet. Um andere Verbindungen zu nutzen, muss der Benutzer sie explizit als Methodenparameter übergeben. Der Benutzer kann zudem beliebig viele Verbindungen erstellen. Pro Thread kann nur eine Standardverbindung verwendet werden. Bitte beachten Sie, dass Standardverbindungen korrekt funktionieren, wenn der Benutzer Thread Objekte für Multitasking‑Programmierung. Wenn der Benutzer einen Connection‑Pool oder Task Objekte für Multitasking, dieser Modus kann zu falschem Verhalten führen. Um dieses Problem zu vermeiden, muss der Benutzer die Standardverbindung am Ende der Codeausführung manuell disposen.

Empfehlungen

Wenn der Benutzer alle Befehle an die Hauptverbindung sendet, kann es zu einer Situation kommen, in der Befehle verschiedener Threads vermischt werden. Der Benutzer sollte verstehen, welche Befehle von ihrer Reihenfolge abhängen und Maßnahmen zur Synchronisation solcher Befehle ergreifen. Es muss auch die Möglichkeit berücksichtigt werden, Befehle in verschiedenen Sitzungen (IMAP/POP3) auszuführen. Die zeitintensivsten Vorgänge sind FetchMessage, AppendMessage, und Send. Es ist wahrscheinlich sinnvoll, diese Vorgänge mit einem neuen Thread und einer neuen Verbindung auszuführen. Schnelle Vorgänge wie Delete es macht Sinn, dies mit der Hauptverbindung auszuführen. Bitte beachten Sie, dass die Initialisierung einer neuen Verbindung ein recht zeitaufwändiger Vorgang ist.