Podpora multithreadingu a pool spojení v e‑mailových klientech

Poštovní klienti jako ImapClient, Pop3Client, a SmtpClient lze použít v multithreadovém prostředí. Klient může udržovat jedno nebo více spojení se serverem. Pro správu sady spojení v klientovi se používá pool spojení. Počet spojení, která mohou být vytvořena a použita současně, je omezen CredentialsByHostClient.MaxConnectionsPerServer vlastnost. Tato vlastnost může být nastavena na 1 nebo vyšší hodnotu. Implicitně je rovna 10.

Fronta příkazů je implementována pro každé spojení, aby podporovala multithreadové operace. Příkazy implementují nejjednodušší operace definované v protokolu, jako jsou Noop, Authenticate, a tak dále. Uživatel může spustit provádění více příkazů, než je dostupných spojení, ale budou vykonány až když klient bude schopen vytvořit spojení pro operaci.

Jak se poštovní klienti chovají v multithreadovém prostředí

E‑mailoví klienti mají následující chování:

  1. Když MaxConnectionsPerServer = 1, klient vytvoří jedno spojení a provede autentizaci a autorizaci. Toto spojení zůstává v pracovním stavu, dokud není klient uvolněn. Všechny operace z různých vláken jsou směrovány do jedné fronty příkazů umístěné v hlavním spojení.

  2. Když MaxConnectionsPerServer > 1, klient vytvoří požadovaný počet spojení a provede autentizaci a autorizaci pro každé spojení. Jedno spojení je rezervováno jako hlavní spojení. Toto spojení zůstává v pracovním stavu, dokud není klient uvolněn. Všechna ostatní spojení jsou vytvářena a uvolňována na vyžádání. Maximální počet takových spojení je definován MaxConnectionsPerServer vlastnost. Například pokud MaxConnectionsPerServer = 2, pak je jedno spojení rezervováno jako hlavní spojení a druhé jako doplňkové pro operace prováděné v jiných vláknech. V důsledku toho, pokud MaxConnectionsPerServer = 3, pak je první spojení rezervováno jako hlavní spojení a dvě další spojení jsou použita jako doplňková pro operace prováděné v jiných vláknech. Když požadavek na spojení přijde z nového vlákna a všechna spojení jsou již využita, klient čeká, dokud se počet použitých spojení nesníží. To je velmi důležitý okamžik, který objasňuje, proč je správné uvolňování spojení tak důležité.

Příklady použití poštovních klientů v multithreadovém prostředí

Uživatel může provádět operace v různých vláknech několika způsoby. Lze je rozdělit do dvou typů.

Použití asynchronních (Begin/End) metod

Uživatel používá asynchronní (Begin/End) metody definované v klientu. V tomto případě poštovní klient spouští nové vlákna podle potřeby. Ve klientovi je implementována fronta úkolů (neplést si ji s frontou příkazů ve spojení). Úkol může být proveden, pokud je k dispozici spojení. Jakmile počet použitých spojení klesne pod limitní hodnotu, klient vytvoří nové spojení, vytvoří vlákno pro aktuální úkol a úkol provede. Příklad použití asynchronních operací:

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

Použití uživatelem vytvořených vláken

Uživatel může vytvářet vlákna pomocí objektů jako Thread, ThreadPool, Task, nebo jakékoli jiné objekty určené k tomuto účelu. Uživatel může také používat vlákna vytvořená v kódu třetích stran. V takovém případě má klient dva modely chování.

a. Pokud uživatel nezajistil vytvoření dalších spojení pro operace ve vláknu, všechny operace pro toto vlákno budou odeslány do fronty příkazů hlavního spojení. Následuje příklad operací v dalším vláknu bez vytvoření nového spojení — všechny transakce jsou prováděny přes hlavní spojení:

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. Když uživatel spustí metodu pro vytvoření nového spojení pro další vlákno, toto vlákno je blokováno, dokud se hodnota kvóty pro nová spojení nezmění a neumožní vytvořit nové spojení. Pak je vytvořeno nové spojení, které je nastaveno jako výchozí spojení pro všechny operace v tomto vláknu. Po dokončení všech operací v tomto vláknu musí být spojení uvolněno. Pro vytváření nových spojení použijte CredentialsByHostClient.CreateConnection metodu. Tato metoda vrací objekt, který implementuje IDisposable rozhraní. Pro uvolnění spojení použijte Dispose metoda musí být vyvolána. Vytvoření a uvolnění spojení musí být provedeno uvnitř vlákna, kde jsou prováděny poštovní operace. Pokus o vytvoření nového spojení ve vlákně, kde byl vytvořen poštovní klient, vede k chybě, protože toto vlákno není v tu chvíli určeno k vytváření nových spojení. Vytvořit nové spojení také není možné, když MaxConnectionsPerServer = 1. Příklad kódu pro vytvoření nového spojení v dalším vlákně:

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

Pool spojení

Od verze Aspose.Email 19.3 byl pool spojení refaktorován. EmailClient třída byla zavedena, která postupně nahrazuje CredentialsByHostClient třída. The EmailClient třída poskytuje ConnectionAsgmtMode vlastnosti, která definuje režim alokace spojení ve vícevláknovém prostředí. EmailClient.ConnectionAsgmtMode je nastaveno pomocí ConnectionAsgmtType výčet.

Typy spojení

Existují tři typy spojení:

  • Hlavní spojení. Jedná se o spojení, které je vytvořeno a uvolněno společně s poštovním klientem. Nelze jej vytvářet ani rušit ručně.
  • Výchozí spojení. Uživatel může vytvářet výchozí spojení pro vlákna pomocí CreateConnection metody. Pokud existuje výchozí spojení, všechny metody e‑mailového klienta spuštěné ve vlákně budou toto spojení implicitně používat. Na vlákně může existovat jen jedno výchozí spojení. Může být vytvořeno ručně nebo automaticky, podle EmailClient.ConnectionAsgmtMode vlastnost. Tato spojení lze vytvořit ručně pomocí EmailClient.CreateConnection(createAsDefaultConnection = true) metody. Pokud není výchozí spojení použito (závisí na režimu alokace spojení), místo toho je implicitně použito hlavní spojení.
  • Nezávislá spojení. Jedná se o spojení, která nejsou svázána s vlákny. Mohou být vytvořena ručně a musí být použita explicitně jako parametr metody. Tato spojení lze vytvořit ručně pomocí EmailClient.CreateConnection() metoda nebo EmailClient.CreateConnection(createAsDefaultConnection = false) metoda.

Typy alokace spojení

Pro nastavení EmailClient.ConnectionAsgmtMode vlastnost, ConnectionAsgmtType enumerace se používá. Poskytované typy alokace jsou uvedeny níže.

  • ConnectionAsgmtType.UseMainOrDefault Tento režim je ve výchozím nastavení používán e‑mailovými klienty. Klient používá hlavní spojení pro všechny operace z více vláken, pokud nebylo vytvořeno výchozí spojení, nebo pokud nebylo spojení předáno jako parametr metody explicitně. Hlavní spojení je vytvořeno současně s e‑mailovým klientem. Uživatel může vytvářet výchozí spojení pro vlákna pomocí CreateConnection metodu. Pokud je pro vlákno vytvořeno výchozí spojení, je implicitně použito pro všechny metody e‑mailového klienta vyvolané v tomto vláknu. Pokud výchozí spojení pro vlákno není vytvořeno, hlavní spojení je použito pro všechny metody vyvolané v tomto vláknu. Uživatel může také vytvořit spojení nezávislá na vláknech (ne výchozí spojení) pomocí CreateConnection metodu. Pro použití dalších spojení (nehlavních a ne výchozích) je třeba spojení předat explicitně jako parametr metody. Uživatel může navíc vytvořit libovolný počet spojení. Na vlákně může existovat jen jedno výchozí spojení. Všimněte si, že výchozí spojení fungují správně, pokud uživatel používá Thread objekty pro programování multitaskingu. Pokud uživatel používá pool spojení nebo Task objekty pro multitasking, tento režim může vést k nesprávnému chování. Aby se tomu předešlo, musí uživatel na konci vykonávání kódu ručně uvolnit výchozí spojení (pokud je používáno).

  • ConnectionAsgmtType.UseMain E‑mailový klient používá hlavní spojení pro všechny operace z více vláken. Hlavní spojení je vytvořeno současně s e‑mailovým klientem. Uživatel nemůže vytvářet výchozí spojení, ale může vytvářet spojení nezávislá na vláknech pomocí CreateConnection metody. Pro použití dalších spojení je třeba je předat explicitně jako parametr metody.

  • ConnectionAsgmtType.UseDefault E‑mailový klient používá implicitně pouze výchozí spojení pro všechny operace z více vláken. Hlavní spojení není v tomto režimu použito. Pokud pro vlákno nebylo vytvořeno výchozí spojení (při první volbě metody klienta), klient vytvoří výchozí spojení implicitně pro vlákno před provedením první operace. Uživatel nemůže vytvářet výchozí spojení pro vlákna pomocí CreateConnection metodu, protože jsou vytvářena automaticky. Uživatel může také vytvořit spojení nezávislá na vláknech pomocí CreateConnection metodu. Pro použití dalších spojení je třeba je předat explicitně jako parametr metody. Uživatel může navíc vytvořit libovolný počet spojení. Na vlákně může být použito jen jedno výchozí spojení. Všimněte si, že výchozí spojení fungují správně, pokud uživatel používá Thread objekty pro programování multitaskingu. Pokud uživatel používá pool spojení nebo Task objekty pro multitasking, tento režim může vést k nesprávnému chování. Aby se tomuto problému předešlo, musí uživatel na konci vykonávání kódu ručně uvolnit výchozí spojení.

Doporučení

Pokud uživatel posílá všechny příkazy hlavnímu spojení, může dojít ke smíchání příkazů z různých vláken. Uživatel by měl rozumět, které příkazy jsou závislé na jejich pořadí, a přijmout opatření pro synchronizaci takových příkazů. Je také nutné zvážit možnost spouštění příkazů v různých relacích (IMAP/POP3). Nejvíce časově náročné operace jsou FetchMessage, AppendMessage, a Send. Pravděpodobně má smysl provádět tyto operace v novém vlákně a novém spojení. Rychlé operace jako Delete má smysl provést s hlavním spojením. Všimněte si, že inicializace nového spojení je poměrně časově náročná operace.