Підтримка багатопоточності та пул підключень у поштових клієнтах

Поштові клієнти, такі як 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 клас. The 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 має сенс виконувати разом з основним підключенням. Зверніть увагу, що ініціалізація нового підключення – це доволі затратна у часі операція.