Ondersteuning voor multithreading en connectiepool in e‑mailclients

Mailclients zoals ImapClient, Pop3Client, and SmtpClient kan worden gebruikt in een multithreaded‑omgeving. Een client kan één of meer verbindingen met een server behouden. Om de set van verbindingen binnen een client te beheren, wordt een connectie‑pool gebruikt. Het aantal verbindingen dat tegelijk kan worden aangemaakt en gebruikt, wordt beperkt door de CredentialsByHostClient.MaxConnectionsPerServer eigenschap. Deze eigenschap kan worden ingesteld op 1 of een hogere waarde. Standaard is deze gelijk aan 10.

Er is een opdracht‑wachtrij geïmplementeerd voor elke verbinding om multithreading‑operaties te ondersteunen. Opdrachten implementeren de eenvoudigste operaties die in het protocol zijn gedefinieerd, zoals Noop, Authenticate, enzovoort. Een gebruiker kan meer opdrachten starten dan er beschikbare verbindingen zijn, maar ze worden pas uitgevoerd wanneer de client een verbinding kan aanmaken voor de bewerking.

Hoe mailclients zich gedragen in een multithreaded‑omgeving

E‑mailclients hebben het volgende gedrag:

  1. Wanneer MaxConnectionsPerServer = 1, maakt de client één verbinding aan en voert authenticatie en autorisatie uit. Deze verbinding blijft actief tot de client wordt vrijgegeven. Alle bewerkingen vanuit verschillende threads worden naar één opdracht‑wachtrij gestuurd die zich in de hoofdverbinding bevindt.

  2. Wanneer MaxConnectionsPerServer > 1, maakt de client het vereiste aantal verbindingen aan en voert authenticatie en autorisatie uit voor elke verbinding. Eén verbinding wordt gereserveerd als de hoofdverbinding. Deze verbinding blijft actief tot de client wordt vrijgegeven. Alle andere verbindingen worden on-demand aangemaakt en vrijgegeven. Het maximum aantal dergelijke verbindingen wordt bepaald door de MaxConnectionsPerServer eigenschap. Bijvoorbeeld, als MaxConnectionsPerServer = 2, dan wordt één verbinding gereserveerd als de hoofdverbinding, en een tweede verbinding wordt gebruikt als extra verbinding voor bewerkingen die in andere threads worden uitgevoerd. Dienovereenkomstig, als MaxConnectionsPerServer = 3, dan wordt de eerste verbinding gereserveerd als de hoofdverbinding, en twee andere verbindingen worden gebruikt als extra verbindingen voor bewerkingen die in andere threads worden uitgevoerd. Wanneer een verzoek om een verbinding van een nieuwe thread komt en alle verbindingen al in gebruik zijn, wacht de client totdat het aantal gebruikte verbindingen afneemt. Dit is een heel belangrijk moment dat verduidelijkt waarom het correct vrijgeven van verbindingen zo belangrijk is.

Voorbeelden van het gebruik van mailclients in een multithreaded‑omgeving

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

Gebruik van asynchrone (Begin/End) methoden

Een gebruiker gebruikt de asynchrone (Begin/End) methoden die in de client gedefinieerd zijn. In dit geval start de mailclient nieuwe threads wanneer dat nodig is. Er wordt een taak‑wachtrij geïmplementeerd in de client (verwarring met de opdracht‑wachtrij in de connectie vermijden). Een taak kan worden uitgevoerd als er een verbinding beschikbaar is. Zodra het aantal gebruikte verbindingen onder de limiet komt, maakt de client een nieuwe verbinding, maakt een thread voor de huidige taak, en voert die taak uit. Een voorbeeld van het gebruik van asynchrone operaties:

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

Gebruik van door de gebruiker aangemaakte threads

Een gebruiker kan threads aanmaken met objecten zoals Thread, ThreadPool, Task, of andere objecten bedoeld voor dit doel. Een gebruiker kan ook threads gebruiken die in third‑party code zijn aangemaakt. In dat geval heeft de client twee gedrag­modellen.

a. Als de gebruiker geen extra verbindingen heeft aangemaakt voor bewerkingen in de thread, worden alle bewerkingen voor deze thread naar de opdrachtwachtrij van de hoofdverbinding gestuurd. Het volgende is een voorbeeld van bewerkingen in een extra thread zonder een nieuwe verbinding aan te maken ‑ alle transacties worden via de hoofdverbinding uitgevoerd:

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. Wanneer de gebruiker een methode uitvoert om een nieuwe verbinding voor een extra thread te maken, wordt die thread geblokkeerd totdat de quotawaarde voor nieuwe verbindingen wijzigt zodat een nieuwe verbinding mogelijk is. Vervolgens wordt er een nieuwe verbinding aangemaakt. Deze verbinding wordt ingesteld als de standaardverbinding voor alle bewerkingen in deze thread. Nadat alle bewerkingen in deze thread zijn voltooid, moet de verbinding worden vrijgegeven. Gebruik de CredentialsByHostClient.CreateConnection methode. Deze methode retourneert een object dat de IDisposable interface. Om de verbinding vrij te geven, de Dispose methode moet worden aangeroepen. Het aanmaken en vrijgeven van een verbinding moet plaatsvinden binnen de thread waarin de mail‑operaties worden uitgevoerd. Een poging om een nieuwe verbinding aan te maken in de thread waarin de mailclient is aangemaakt leidt tot een fout, omdat die thread op dat moment geen nieuwe verbinding kan aanmaken. Een nieuwe verbinding kan ook niet worden aangemaakt wanneer MaxConnectionsPerServer = 1. Een code‑voorbeeld voor het aanmaken van een nieuwe verbinding in een extra 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));
    }
});

Connectie‑pool

Vanaf Aspose.Email 19.3 is de connectie‑pool geherstructureerd. De EmailClient klasse werd geïntroduceerd, die uiteindelijk de CredentialsByHostClient klasse. De EmailClient klasse biedt een ConnectionAsgmtMode eigenschap die de modus van connection‑allocation definieert in een multithreaded‑omgeving. EmailClient.ConnectionAsgmtMode wordt ingesteld met de ConnectionAsgmtType enumeratie.

Verbindingstypen

Er zijn drie verbindingstypen:

  • De hoofdverbinding. Dit is de verbinding die samen met de mailclient wordt aangemaakt en vrijgegeven. Deze kan niet handmatig worden aangemaakt of vrijgegeven.
  • Standaardverbinding. Een gebruiker kan standaardverbindingen voor threads aanmaken met de CreateConnection methode. Als er een standaardverbinding bestaat, zullen alle methoden van de e‑mailclient die in een thread worden uitgevoerd impliciet deze verbinding gebruiken. Per thread kan slechts één standaardverbinding bestaan. Deze kan handmatig of automatisch worden aangemaakt, afhankelijk van de EmailClient.ConnectionAsgmtMode eigenschap. Deze verbindingen kunnen handmatig worden aangemaakt met de EmailClient.CreateConnection(createAsDefaultConnection = true) methode. Als er geen standaardverbinding wordt gebruikt (afhankelijk van de connectietoewijzingsmodus), wordt in plaats daarvan de hoofdverbinding impliciet gebruikt.
  • Onafhankelijke verbindingen. Dit zijn verbindingen die niet aan threads zijn gekoppeld. Ze kunnen handmatig worden aangemaakt en moeten expliciet als methode‑parameter worden gebruikt. Deze verbindingen kunnen handmatig worden aangemaakt met de EmailClient.CreateConnection() methode of de EmailClient.CreateConnection(createAsDefaultConnection = false) methode.

Connection‑toewijzingstypen

Om de EmailClient.ConnectionAsgmtMode eigenschap, de ConnectionAsgmtType enumeratie wordt gebruikt. De toewijzingstypen die het biedt, staan hieronder vermeld.

  • ConnectionAsgmtType.UseMainOrDefault Deze modus is standaard in e‑mailclients. De e‑mailclient gebruikt de hoofdverbinding voor alle bewerkingen vanuit meerdere threads als er geen standaardverbinding is aangemaakt, of als er geen verbinding expliciet als methode‑parameter is doorgegeven. De hoofdverbinding wordt gelijktijdig met de e‑mailclient aangemaakt. De gebruiker kan standaardverbindingen voor threads aanmaken met de CreateConnection methode. Als een standaardverbinding voor een thread wordt aangemaakt, wordt deze impliciet gebruikt voor alle methoden van de e‑mailclient die in die thread worden aangeroepen. Als er geen standaardverbinding voor een thread is aangemaakt, wordt de hoofdverbinding gebruikt voor alle methoden die in die thread worden aangeroepen. De gebruiker kan ook verbindingen aanmaken die niet aan threads zijn gekoppeld (geen standaardverbindingen) met de CreateConnection methode. Om andere verbindingen (niet hoofd‑ en niet standaard) te gebruiken, moet de gebruiker de verbinding expliciet doorgeven als parameter van de methode. De gebruiker kan bovendien een willekeurig aantal verbindingen aanmaken. Per thread kan slechts één standaardverbinding bestaan. Let op dat standaardverbindingen correct werken als de gebruiker Thread objecten voor multitasking‑programmering. Als de gebruiker een connectie‑pool of Task objecten voor multitasking, dit kan leiden tot onjuist gedrag. Om dit probleem te vermijden, moet de gebruiker de standaardverbinding (indien gebruikt) handmatig vrijgeven aan het einde van de code‑executie.

  • ConnectionAsgmtType.UseMain De e‑mailclient gebruikt de hoofdverbinding voor alle bewerkingen vanuit meerdere threads. De hoofdverbinding wordt gelijktijdig met de e‑mailclient aangemaakt. De gebruiker kan geen standaardverbindingen aanmaken, maar kan wel verbindingen aanmaken die niet aan threads zijn gekoppeld met de CreateConnection methode. Om andere verbindingen te gebruiken, moet de gebruiker ze expliciet als methode‑parameter doorgeven.

  • ConnectionAsgmtType.UseDefault De e‑mailclient gebruikt alleen standaardverbindingen impliciet voor alle bewerkingen vanuit meerdere threads. De hoofdverbinding wordt in deze modus niet gebruikt. Als er geen standaardverbinding is aangemaakt voor een thread (bij de eerste aanroep van een e‑mailclient‑methode), maakt de e‑mailclient een standaardverbinding impliciet aan voor die thread voordat de eerste bewerking wordt uitgevoerd. De gebruiker kan geen standaardverbindingen voor threads aanmaken met de CreateConnection methode omdat ze automatisch worden aangemaakt. De gebruiker kan ook verbindingen aanmaken die niet aan threads zijn gekoppeld met de CreateConnection methode gebruikt. Om andere verbindingen te gebruiken, moet de gebruiker ze expliciet als methode‑parameter doorgeven. De gebruiker kan bovendien een willekeurig aantal verbindingen aanmaken. Per thread kan slechts één standaardverbinding worden gebruikt. Let op dat standaardverbindingen correct werken als de gebruiker Thread objecten voor multitasking‑programmering. Als de gebruiker een connectie‑pool of Task objecten voor multitasking, dit kan leiden tot onjuist gedrag. Om dit probleem te vermijden, moet de gebruiker de standaardverbinding handmatig vrijgeven aan het einde van de code‑executie.

Aanbevelingen

Als de gebruiker alle opdrachten naar de hoofdverbinding stuurt, kan er een situatie ontstaan waarbij opdrachten van verschillende threads door elkaar lopen. De gebruiker moet begrijpen welke opdrachten van hun volgorde afhankelijk zijn en maatregelen nemen om dergelijke opdrachten te synchroniseren. Het is ook noodzakelijk om de mogelijkheid te overwegen om opdrachten uit te voeren in verschillende sessies (IMAP/POP3). De meest tijdrovende operaties zijn FetchMessage, AppendMessage, and Send. Het is waarschijnlijk logisch om deze bewerkingen met een nieuwe thread en een nieuwe verbinding uit te voeren. Snelle bewerkingen zoals Delete zouden logisch kunnen zijn om uit te voeren met de hoofdverbinding. Houd er rekening mee dat het initialiseren van een nieuwe verbinding een redelijk tijdrovende operatie is.