Поддръжка на многопоточност и пул за връзки в имейл клиенти

Имейл клиенти като ImapClient, Pop3Client, и SmtpClient може да се използва в многонитова среда. Клиентът може да поддържа една или повече връзки със сървъра. За управление на набора от връзки в клиент, се използва пул за връзки. Броят на връзките, които могат да бъдат създадени и използвани едновременно, се ограничава от CredentialsByHostClient.MaxConnectionsPerServer свойство. Това свойство може да бъде зададено на 1 или по‑голяма стойност. По подразбиране е 10.

Опашка за команди се имплементира за всяка връзка, за да подкрепи многонитови операции. Командите изпълняват най‑опростените операции, дефинирани в протокола, като Noop, Authenticate, и т.н. Потребителят може да започне изпълнение на повече команди, отколкото има налични връзки, но те ще се изпълнят само когато клиентът може да създаде връзка за операцията.

Как имейл клиентите се държат в многонитова среда

Имейл клиентите имат следното поведение:

  1. Когато MaxConnectionsPerServer = 1, клиентът създава една връзка и извършва удостоверяване и упълномощаване. Тази връзка се поддържа в работно състояние, докато клиентът не бъде освободен. Всички операции от различни нишки се насочват към една опашка за команди, поставена в главната връзка.

  2. Когато MaxConnectionsPerServer > 1, клиентът създава необходимия брой връзки и извършва удостоверяване и упълномощаване за всяка връзка. Една връзка се запазва като главна. Тази връзка се поддържа в работно състояние, докато клиентът не бъде освободен. Всички останали връзки се създават и освобождават при нужда. Максималният брой такива връзки се определя от MaxConnectionsPerServer свойство. Например, ако MaxConnectionsPerServer = 2, тогава една връзка се запазва като главна, а втората се използва като допълнителна за операции, изпълнявани в други нишки. Съответно, ако MaxConnectionsPerServer = 3, тогава първата връзка се запазва като главна, а две други връзки се използват като допълнителни за операции, изпълнявани в други нишки. Когато заявка за връзка идва от нова нишка и всички връзки вече са заети, клиентът изчаква докато броят използвани връзки намалее. Това е много важен момент, който разяснява защо правилното освобождаване на връзките е толкова важно.

Примери за използване на имейл клиенти в многонитова среда

Потребителят може да изпълнява операции в различни нишки по няколко начина. Те могат да се разделят на два типа.

Използване на асинхронни (Begin/End) методи

Потребителят използва асинхронните (Begin/End) методи, дефинирани в клиента. В този случай имейл клиентът създава нови нишки при нужда. Опашка за задачи се имплементира в клиента (не бъркайте я с опашка за команди във връзката). Задача може да бъде изпълнена, ако е достъпна връзка. След като броят използвани връзки стане по-малък от лимита, клиентът създава нова връзка, създава нишка за текущата задача и я изпълнява. Пример за използване на асинхронни операции:

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

Използване на създадени от потребителя нишки

Потребителят може да създава нишки, използвайки обекти като Thread, ThreadPool, Task, или други обекти, предназначени за тази цел. Потребителят може също да използва нишки, създадени в трети страни. В този случай клиентът има два модела на поведение.

a. Ако потребителят не е осигурил създаването на допълнителни връзки за операциите в нишката, всички операции за тази нишка ще бъдат изпратени към опашката за команди на главната връзка. Следва пример за операции в допълнителна нишка без създаване на нова връзка – всички транзакции се извършват чрез главната връзка:

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. Когато потребителят изпълни метод за създаване на нова връзка за допълнителна нишка, тази нишка е блокирана, докато стойността на квотата за нови връзки се промени, за да позволи нова връзка. След това се създава нова връзка. Тази връзка се задава като основна връзка за всички операции в тази нишка. След като всички операции в тази нишка завършат, връзката трябва да бъде освободена. За създаване на нови връзки използвайте CredentialsByHostClient.CreateConnection метод. Този метод връща обект, който имплементира IDisposable интерфейс. За освобождаване на връзката, Dispose метод трябва да бъде извикан. Създаването и освобождаването на връзка трябва да се изпълнява вътре в нишката, където се изпълняват пощенските операции. Опит за създаване на нова връзка в нишката, в която е създаден имейл клиентът, води до грешка, тъй като тази нишка не може да се използва за създаване на нова връзка в момента. Създаването на нова връзка също не е възможно, когато MaxConnectionsPerServer = 1. Пример за код, създаващ нова връзка в допълнителна нишка:

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

Пул за връзки

Започвайки с Aspose.Email 19.3, пулът за връзки беше пренареден. EmailClient клас беше въведен, който в крайна сметка заменя CredentialsByHostClient клас. Този EmailClient класът предоставя ConnectionAsgmtMode свойството, което определя режима за разпределяне на връзките в многонитова среда. EmailClient.ConnectionAsgmtMode се задава чрез ConnectionAsgmtType изброяване.

Типове връзки

Има три типа връзки:

  • Главната връзка. Това е връзката, създадена и освобождавана заедно с имейл клиента. Не може да бъде създадена или освободена ръчно.
  • Основна връзка. Потребителят може да създава основни връзки за нишки чрез CreateConnection метод. Ако съществува основна връзка, всички методи на имейл клиента, изпълнявани в нишка, ще я използват имплицитно. Само една основна връзка може да съществува на нишка. Тя може да бъде създадена ръчно или автоматично, в зависимост от EmailClient.ConnectionAsgmtMode свойство. Тези връзки могат да бъдат създадени ръчно с EmailClient.CreateConnection(createAsDefaultConnection = true) метод. Ако не се използва основна връзка (зависи от режима на разпределение на връзките), вместо това се използва имплицитно главната връзка.
  • Независими връзки. Това са връзки, които не са свързани с нишки. Те могат да бъдат създадени ръчно и трябва да се използват изрично като параметър на метода. Тези връзки могат да бъдат създадени ръчно с EmailClient.CreateConnection() метод или EmailClient.CreateConnection(createAsDefaultConnection = false) метод.

Типове за разпределяне на връзки

За да настроите EmailClient.ConnectionAsgmtMode свойство, ConnectionAsgmtType изброяването се използва. Видовете за разпределение, които предоставя, са изброени по-долу.

  • ConnectionAsgmtType.UseMainOrDefault Този режим се използва по подразбиране в имейл клиентите. Имейл клиентът използва главната връзка за всички операции от множество нишки, ако не е създадена основна връзка или ако връзка не е предадена като параметър на метода изрично. Главната връзка се създава едновременно с имейл клиента. Потребителят може да създава основни връзки за нишки чрез CreateConnection метод. Ако за нишка е създадена основна връзка, тя се използва имплицитно за всички методи на имейл клиента, извиквани в тази нишка. Ако за нишка не е създадена основна връзка, главната връзка се използва за всички методи, извиквани в тази нишка. Потребителят може също така да създава връзки, които не са свързани с нишки (не основни връзки), чрез CreateConnection метод. За използване на други връзки (не главна и не основна), потребителят трябва да предаде връзката изрично като параметър на метода. Потребителят може допълнително да създаде произволен брой връзки. Само една основна връзка може да съществува на нишка. Моля, имайте предвид, че основните връзки работят правилно, ако потребителят използва Thread обекти за програмиране с многозадачност. Ако потребителят използва пул за връзки или Task обекти за многозадачност, този режим може да доведе до некоректно поведение. За избягване на този проблем потребителят трябва ръчно да освободи основната връзка (ако се използва) в края на изпълнението на кода.

  • ConnectionAsgmtType.UseMain Имейл клиентът използва главната връзка за всички операции от множество нишки. Главната връзка се създава едновременно с имейл клиента. Потребителят не може да създава основни връзки, но може да създава връзки, които не са свързани с нишки, чрез CreateConnection метод. За използване на други връзки потребителят трябва да ги предаде изрично като параметър на метода.

  • ConnectionAsgmtType.UseDefault Имейл клиентът използва само основни връзки имплицитно за всички операции от множество нишки. Главната връзка не се използва в този режим. Ако за нишка не е създадена основна връзка (при първото извикване на метод на имейл клиента), имейл клиентът създава основна връзка имплицитно за нишката преди първата операция. Потребителят не може да създава основни връзки за нишки с CreateConnection метод, защото те се създават автоматично. Потребителят може също така да създава връзки, които не са свързани с нишки, чрез CreateConnection метод. За използване на други връзки потребителят трябва да ги предаде изрично като параметър на метода. Потребителят може допълнително да създаде произволен брой връзки. Само една основна връзка може да се използва на нишка. Моля, имайте предвид, че основните връзки работят правилно, ако потребителят използва Thread обекти за програмиране с многозадачност. Ако потребителят използва пул за връзки или Task обекти за многозадачност, този режим може да доведе до некоректно поведение. За избягване на този проблем потребителят трябва ръчно да освободи основната връзка в края на изпълнението на кода.

Препоръки

Ако потребителят изпраща всички команди към главната връзка, може да възникне ситуация, при която команди от различни нишки се смесват. Потребителят трябва да разбере кои команди зависят от последователността им и да предприеме мерки за синхронизирането им. Също така е необходимо да се обмисли възможността за изпълнение на команди в различни сесии (IMAP/POP3). Най-времеемките операции са FetchMessage, AppendMessage, и Send. Вероятно има смисъл да се изпълнят тези операции в нова нишка и с нова връзка. Бързи операции като Delete има смисъл да се изпълнява с главната връзка. Моля, имайте предвид, че инициализацията на нова връзка е доста времеемка операция.