Mehrthread‑Unterstützung in Mail‑Clients

Mail‑Clients wie ImapClient und Pop3Client ermöglichen es Benutzern, sie in einer Multi‑Threading‑Umgebung zu verwenden. Mail‑Clients erlauben eine oder mehrere Verbindungen zu einem Server. Um ein Set von Verbindungen innerhalb von Clients zu verwalten, wird ein Connection‑Pool verwendet. Die Anzahl der Verbindungen, die gleichzeitig erstellt und genutzt werden können, ist durch die Eigenschaft CredentialsByHostClient.MaxConnectionsPerServer begrenzt. Diese Eigenschaft kann auf 1 oder einen höheren Wert gesetzt werden. Standardmäßig ist dieser Wert 10. Für die Verbindung wurde eine Befehlswarteschlange implementiert, um Multi‑Threading‑Operationen zu unterstützen. Befehle implementieren einfachste im Protokoll definierte Operationen, wie Noop, Authenticate usw. Der Benutzer kann mehr Befehle gleichzeitig ausführen, als Verbindungen verfügbar sind. Die Befehle werden jedoch erst ausgeführt, wenn der Client in der Lage ist, eine Verbindung für die Operation zu erstellen.

Methoden zur Verwendung von Mail‑Clients in einer Multi‑Threading‑Umgebung

E‑Mail‑Clients haben das folgende Verhalten:

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

2. Falls MaxConnectionsPerServer > 1, erstellt der Client die benötigte Anzahl von Verbindungen, führt Authentifizierung und Autorisierung für jede Verbindung durch. Eine Verbindung wird als Hauptverbindung reserviert. Diese Verbindung bleibt im Arbeitszustand, bis der Client disposed wird. Alle anderen Verbindungen werden bei Bedarf erstellt und disposed. Die maximale Anzahl solcher Verbindungen wird durch die Eigenschaft MaxConnectionsPerServer definiert, d.h. zum Beispiel, wenn MaxConnectionsPerServer = 2, dann ist eine als Hauptverbindung reserviert und die zweite wird als zusätzliche für Vorgänge in anderen Threads verwendet. Entsprechend, wenn MaxConnectionsPerServer = 3, dann ist die erste Verbindung als Hauptverbindung reserviert und die beiden anderen Verbindungen werden als zusätzliche für Vorgänge in anderen Threads verwendet. Falls ein neuer Verbindungswunsch von einem neuen Thread kommt und alle Verbindungen bereits belegt sind, wartet der Client, bis die Anzahl der genutzten Verbindungen verringert wird. Dieser wichtige Punkt erklärt, warum das korrekte Freigeben von Verbindungen so wichtig ist.

Beispiele

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

1. Der Benutzer verwendet asynchrone (Begin/End) Methoden, die im Client definiert sind. In diesem Fall startet der Mail‑Client bei Bedarf neue Threads. Im Client ist eine Aufgabenwarteschlange implementiert (bitte nicht mit der Befehlswarteschlange in der Verbindung verwechseln). 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. Beispiel für die Verwendung asynchroner Vorgänge:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.setHost("domain.com");
client.setUsername("username");
client.setPassword("password");
client.selectFolder("InBox");

ImapMessageInfoCollection messages = client.listMessages();
IAsyncResult res1 = client.beginFetchMessage(messages.get_Item(0).getUniqueId());
IAsyncResult res2 = client.beginFetchMessage(messages.get_Item(1).getUniqueId());
MailMessage msg1 = client.endFetchMessage(res1);
MailMessage msg2 = client.endFetchMessage(res2);

2. Der Benutzer kann Threads mit Objekten wie Thread, ExecutorService oder anderen dafür vorgesehenen Objekten erstellen. Außerdem kann der Benutzer Threads verwenden, die im 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. Ein Beispiel für Vorgänge in einem zusätzlichen Thread ohne Erstellung einer neuen Verbindung, alle Transaktionen werden über die Hauptverbindung durchgeführt:

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
private static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        client.selectFolder("folderName");
        ImapMessageInfoCollection messageInfoCol = client.listMessages();
        for (ImapMessageInfo messageInfo : messageInfoCol) {
            list.add(client.fetchMessage(messageInfo.getUniqueId()));
        }
    }
});

b. Wenn der Benutzer die Methode aufruft, um für einen zusätzlichen Thread eine neue Verbindung zu erstellen, wird dieser Thread blockiert, bis der Quotenwert für neue Verbindungen geändert wird, um eine neue Verbindung zu ermöglichen. Dann wird die neue Verbindung erstellt. Diese Verbindung wird als Standardverbindung für alle Vorgänge in diesem Thread festgelegt. Nachdem alle Vorgänge in diesem Thread abgeschlossen sind, muss die Verbindung freigegeben werden. Zum Erstellen neuer Verbindungen verwenden Sie die Methode CredentialsByHostClient.CreateConnection. Diese Methode gibt ein Objekt zurück, das das IDisposable‑Interface implementiert. Um die Verbindung freizugeben, muss die Dispose‑Methode aufgerufen werden. Erstellung und Freigabe der Verbindung müssen im Thread durchgeführt werden, in dem die Mail‑Operationen ausgeführt werden. Der Versuch, eine neue Verbindung im Thread zu erstellen, in dem der Mail‑Client erstellt wurde, führt zu einem Fehler, weil dieser Thread zu diesem Zeitpunkt nicht für das Erstellen einer neuen Verbindung verwendet werden kann. Außerdem ist das Erstellen einer neuen Verbindung nicht möglich, wenn MaxConnectionsPerServer = 1. Ein Codebeispiel zum Erstellen einer neuen Verbindung in einem zusätzlichen Thread:

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        IConnection connection = client.createConnection();
        try {
            client.selectFolder(connection, "FolderName");
            ImapMessageInfoCollection messageInfoCol = client.listMessages(connection);
            for (ImapMessageInfo messageInfo : messageInfoCol)
                list.add(client.fetchMessage(connection, messageInfo.getUniqueId()));
        } finally {
            connection.dispose();
        }
    }
});

Empfehlungen

Es ist zu beachten, dass wenn der Benutzer alle Befehle über die Hauptverbindung sendet, eine Situation entstehen kann, in der Befehle aus verschiedenen Threads vermischt werden. Der Benutzer sollte verstehen, welche Befehle von ihrer Reihenfolge abhängen, und Maßnahmen zur Synchronisation solcher Befehle ergreifen. Es ist ebenfalls nötig, die Möglichkeit der Ausführung von Befehlen in verschiedenen Sitzungen (IMAP/POP3) zu berücksichtigen. Die zeitintensivsten Operationen, wie FetchMessage, AppendMessage und Send, sollten wahrscheinlich in einem neuen Thread und einer neuen Verbindung durchgeführt werden. Schnellere Operationen wie Delete können jedoch mit der Hauptverbindung ausgeführt werden. Bitte beachten Sie, dass die Initialisierung einer neuen Verbindung bereits eine zeitintensive Operation ist.