Ondersteuning voor multithreading in e-mailclients

E‑mailclients zoals ImapClient en Pop3Client Sta gebruikers toe ze te gebruiken in een multithreading‑omgeving. E‑mailclients kunnen één of meerdere verbindingen met een server hebben. Om een set verbindingen binnen clients te beheren, wordt een connection‑pool gebruikt. Het aantal verbindingen dat gelijktijdig kan worden aangemaakt en gebruikt, wordt beperkt door de eigenschap CredentialsByHostClient.MaxConnectionsPerServer. Deze eigenschap kan worden ingesteld op 1 of een hogere waarde. Standaard is deze eigenschap gelijk aan 10. Er is een commando‑wachtrij geïmplementeerd voor de verbinding om multithreading‑bewerkingen te ondersteunen. Commando’s implementeren de eenvoudigste bewerkingen die in het protocol zijn gedefinieerd, zoals Noop, Authenticate enzovoort. De gebruiker kan het uitvoeren van meer commando’s starten dan er beschikbare verbindingen zijn. Maar ze zullen pas worden uitgevoerd wanneer de client een verbinding kan maken voor de bewerking.

Methoden voor het gebruiken van e‑mailclients in een multithreading‑omgeving

E‑mailclients hebben het volgende gedrag:

1. In het geval dat MaxConnectionsPerServer = 1, maakt de client één verbinding aan, voert authenticatie en autorisatie uit. Deze verbinding blijft in werkende staat tot de client wordt verwijderd. Alle bewerkingen van verschillende threads worden naar één commando‑wachtrij gestuurd die zich in de hoofdverbinding bevindt.

2. In het geval dat MaxConnectionsPerServer > 1, maakt de client het benodigde aantal verbindingen aan, voert authenticatie en autorisatie uit voor elke verbinding. Eén verbinding wordt gereserveerd als hoofdverbinding. Deze verbinding blijft in werkende staat totdat de client wordt verwijderd. Alle andere verbindingen worden op aanvraag aangemaakt en verwijderd. Het maximale aantal dergelijke verbindingen wordt bepaald door de eigenschap MaxConnectionsPerServer, dus bijvoorbeeld als MaxConnectionsPerServer = 2, dan is één gereserveerd als hoofdverbinding en wordt de tweede verbinding als extra gebruikt voor bewerkingen die in andere threads worden uitgevoerd. Evenzo, als MaxConnectionsPerServer = 3, dan is de eerste verbinding gereserveerd als hoofdverbinding en worden de twee andere verbindingen als extra gebruikt voor bewerkingen in andere threads. Komt er een nieuw verzoek om een verbinding van een nieuwe thread en zijn alle verbindingen al in gebruik, dan wacht de client tot het aantal gebruikte verbindingen wordt verminderd. Dit is een zeer belangrijk moment dat verduidelijkt waarom het correct vrijgeven van verbindingen zo belangrijk is.

Voorbeelden

De gebruiker kan bewerkingen in verschillende threads op verschillende manieren uitvoeren. Ze kunnen worden onderverdeeld in twee typen:

1. De gebruiker gebruikt asynchrone (Begin/End) methoden die in de client zijn gedefinieerd. In dit geval start de e‑mailclient nieuwe threads wanneer nodig. In de client is een taak‑wachtrij geïmplementeerd (let op, dit is niet te verwarren met de commando‑wachtrij in de verbinding). Een taak kan worden uitgevoerd als er een verbinding beschikbaar is. Zodra het aantal gebruikte verbindingen onder de limietwaarde komt, maakt de client een nieuwe verbinding, start een thread voor de huidige taak en voert deze taak uit. Voorbeeld van het gebruik van asynchrone bewerkingen:

// 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. De gebruiker kan threads maken met objecten zoals Thread, ExecutorService of andere daarvoor bedoelde objecten. Ook kan de gebruiker threads gebruiken die zijn gecreëerd in derde‑partijcode. Hierbij heeft de client twee gedragspatronen

a. Als de gebruiker geen extra verbindingen heeft aangemaakt voor bewerkingen in de thread, worden alle bewerkingen voor deze thread naar de commando‑wachtrij van de hoofdverbinding gestuurd. Een voorbeeld van bewerkingen in een extra thread zonder een nieuwe verbinding te creëren; alle transacties worden via de hoofdverbinding uitgevoerd:

/**
 * 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. Wanneer de gebruiker een methode uitvoert om een nieuwe verbinding te maken voor een extra thread, wordt deze thread geblokkeerd totdat de quotawaarde voor nieuwe verbindingen wordt aangepast om een nieuwe verbinding toe te staan. Vervolgens wordt de nieuwe verbinding aangemaakt. Deze verbinding wordt ingesteld als standaardverbinding voor alle bewerkingen in deze thread. Nadat alle bewerkingen in deze thread zijn voltooid, moet de verbinding worden vrijgegeven. Gebruik de methode CredentialsByHostClient.CreateConnection om nieuwe verbindingen te maken. Deze methode retourneert een object dat de IDisposable‑interface implementeert. Om de verbinding vrij te geven, moet de Dispose‑methode worden aangeroepen. Het aanmaken en vrijgeven van de verbinding moet worden uitgevoerd binnen de thread waarin de e‑mailbewerkingen worden uitgevoerd. Proberen een nieuwe verbinding te maken in de thread waarin de e‑mailclient is gecreëerd leidt tot een fout, omdat deze thread op dat moment niet kan worden gebruikt voor het aanmaken van een nieuwe verbinding. Ook kan er geen nieuwe verbinding worden aangemaakt wanneer MaxConnectionsPerServer = 1. Een codevoorbeeld van het creëren van een nieuwe verbinding in een extra 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();
        }
    }
});

Aanbevelingen

Het is het vermelden waard dat als de gebruiker alle commando’s naar de hoofdverbinding stuurt, er een situatie kan ontstaan waarin commando’s van verschillende threads worden vermengd. De gebruiker moet begrijpen welke commando’s afhankelijk zijn van hun volgorde en maatregelen nemen voor de synchronisatie van dergelijke commando’s. Het is ook noodzakelijk om de mogelijkheid te overwegen om commando’s in verschillende sessies (IMAP/POP3) uit te voeren. De meest tijdrovende bewerkingen, zoals FetchMessage, AppendMessage en Send, hebben waarschijnlijk zin om uit te voeren met een nieuwe thread en een nieuwe verbinding. Maar snelle bewerkingen zoals Delete hebben zin om uit te voeren met de hoofdverbinding. Houd er rekening mee dat het initialiseren van een nieuwe verbinding al een tijdrovende bewerking is.