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

Поштові клієнти, такі як ImapClient і Pop3Client, дозволяють використовувати їх у багатопоточному середовищі. Поштові клієнти можуть мати одне або кілька підключень до сервера. Для керування набором підключень в середині клієнтів використовується пул підключень. Кількість підключень, які можуть бути створені та використані одночасно, обмежується властивістю CredentialsByHostClient.MaxConnectionsPerServer. Це значення можна встановити рівним 1 або більшому. За замовчуванням воно дорівнює 10. Для підключення реалізовано чергу команд, щоб підтримувати багатопоточні операції. Команди реалізують прості операції, визначені протоколом, такі як Noop, Authenticate тощо. Користувач може ініціювати виконання більшої кількості команд, ніж доступних підключень. Однак вони будуть виконані лише тоді, коли клієнт зможе створити підключення для операції.

Методи використання поштових клієнтів у багатопоточному середовищі

Поштові клієнти мають таку поведінку:

1. Якщо MaxConnectionsPerServer = 1, клієнт створює 1 з’єднання, виконує автентифікацію та авторизацію. Це з’єднання підтримується у робочому стані до моменту, коли клієнт буде звільнений. Усі операції з різних потоків направляються в одну чергу команд, розміщену в головному з’єднанні.

2. Якщо MaxConnectionsPerServer > 1, клієнт створює необхідну кількість з’єднань, виконує автентифікацію та авторизацію для кожного з’єднання. Одне з’єднання зарезервовано як головне з’єднання. Це з’єднання підтримується у робочому стані до моменту, коли клієнт буде звільнений. Інші з’єднання створюються та звільняються за потребою. Максимальна кількість таких з’єднань визначається властивістю MaxConnectionsPerServer, наприклад, якщо MaxConnectionsPerServer = 2, то одне зарезервовано як головне з’єднання, а друге використовується як додаткове для операцій, які виконуються в інших потоках. Аналогічно, якщо MaxConnectionsPerServer = 3, то перше з’єднання зарезервовано як головне, а два інші використовуються як додаткові для операцій в інших потоках. У випадку надходження запиту на з’єднання від нового потоку, і коли всі з’єднання вже зайняті, клієнт чекає, доки кількість використаних з’єднань не зменшиться. Це дуже важливий момент, який пояснює, чому правильне звільнення з’єднань є таким важливим.

Приклади

Користувач може виконувати операції в різних потоках кількома способами. Їх можна розділити на два типи:

1. Користувач використовує асинхронні (Begin/End) методи, визначені в клієнті. У цьому випадку поштовий клієнт запускає нові потоки за потреби. У клієнті реалізована черга задач (будь ласка, не плутайте її з чергою команд у з’єднанні). Задача може бути виконана, якщо з’єднання доступне. Коли кількість використаних з’єднань стає меншою за межеве значення, клієнт створює нове з’єднання, створює потік для поточної задачі і виконує її. Приклад використання асинхронних операцій:

2. Користувач може створювати потоки, використовуючи такі об’єкти, як Thread, ThreadPool, Task або будь‑які інші призначені для цього об’єкти. Також користувач може використовувати потоки, створені в сторонньому коді. При цьому клієнт має два моделі поведінки

a. Якщо користувач не створює додаткових з’єднань для операцій у потоці, всі операції для цього потоку будуть відправлені в чергу команд головного з’єднання. Приклад операцій у додатковому потоці без створення нового з’єднання, всі транзакції виконуються через головне з’єднання:

b. Коли користувач виконує метод створення нового з’єднання для додаткового потоку, цей потік блокується, поки значення квоти для нових з’єднань не буде змінено, щоб дозволити нове з’єднання. Потім нове з’єднання створюється. Це з’єднання встановлюється як дефолтне з’єднання для всіх операцій у цьому потоці. Після завершення всіх операцій у цьому потоці з’єднання має бути звільнене. Для створення нових з’єднань використовуйте метод CredentialsByHostClient.CreateConnection. Цей метод повертає об’єкт, який реалізує інтерфейс IDisposable. Для звільнення з’єднання треба викликати метод Dispose. Створення та звільнення з’єднання повинні виконуватись всередині потоку, у якому виконуються поштові операції. Спроба створити нове з’єднання у потоці, в якому був створений поштовий клієнт, призводить до помилки, оскільки цей потік у даний момент не може бути використаний для створення нового з’єднання. Також створення нового з’єднання неможливе, коли MaxConnectionsPerServer = 1. Приклад коду створення нового з’єднання в додатковому потоці:

Рекомендації

Варто зазначити, що якщо користувач надсилає всі команди через головне з’єднання, може виникнути ситуація, коли команди з різних потоків будуть перемішані. Користувач повинен розуміти, які команди залежать від їх послідовності, і вжити заходів для синхронізації таких команд. Також необхідно враховувати можливість виконання команд у різних сеансах (IMAP/POP3). Найбільш ресурсоємними операціями є, наприклад, FetchMessage, AppendMessage та Send. Ймовірно, має сенс виконувати ці операції у новому потоці і новому з’єднанні. Але швидкі операції, такі як Delete, доцільно виконувати через головне з’єднання. Зверніть увагу, що ініціалізація нового з’єднання є достатньо витратною операцією.