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í:
-
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í. -
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ánMaxConnectionsPerServervlastnost. Například pokudMaxConnectionsPerServer = 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, pokudMaxConnectionsPerServer = 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.ConnectionAsgmtModevlastnost. 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 neboEmailClient.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í
CreateConnectionmetodu. 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íCreateConnectionmetodu. 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áThreadobjekty pro programování multitaskingu. Pokud uživatel používá pool spojení neboTaskobjekty 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í
CreateConnectionmetody. 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í
CreateConnectionmetodu, protože jsou vytvářena automaticky. Uživatel může také vytvořit spojení nezávislá na vláknech pomocíCreateConnectionmetodu. 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áThreadobjekty pro programování multitaskingu. Pokud uživatel používá pool spojení neboTaskobjekty 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.