Prise en charge du multithreading et pool de connexions dans les clients de messagerie

Clients de messagerie tels que ImapClient, Pop3Client, et SmtpClient peut être utilisé dans un environnement multithread. Un client peut conserver une ou plusieurs connexions avec un serveur. Pour gérer l’ensemble des connexions à l’intérieur d’un client, un pool de connexion est utilisé. Le nombre de connexions pouvant être créées et utilisées simultanément est limité par le CredentialsByHostClient.MaxConnectionsPerServer propriété. Cette propriété peut être définie à 1 ou à une valeur supérieure. Par défaut, elle vaut 10.

Une file de commandes est implémentée pour chaque connexion afin de prendre en charge les opérations multithreads. Les commandes implémentent les opérations les plus simples définies dans le protocole, telles que Noop, Authenticate, etc. Un utilisateur peut lancer l’exécution de plus de commandes qu’il n’existe de connexions disponibles, mais elles ne seront exécutées que lorsque le client pourra créer une connexion pour l’opération.

Comment les clients de messagerie se comportent dans un environnement multithread

Les clients de messagerie ont le comportement suivant :

  1. Lorsque MaxConnectionsPerServer = 1, le client crée une connexion et effectue l’authentification et l’autorisation. Cette connexion reste en état de fonctionnement jusqu’à ce que le client soit supprimé. Toutes les opérations provenant de différents fils sont dirigées vers une file de commandes placée dans la connexion principale.

  2. Lorsque MaxConnectionsPerServer > 1, le client crée le nombre requis de connexions et effectue l’authentification et l’autorisation pour chaque connexion. Une connexion est réservée comme connexion principale. Cette connexion reste en état de fonctionnement jusqu’à ce que le client soit supprimé. Toutes les autres connexions sont créées et supprimées à la demande. Le nombre maximal de telles connexions est défini par le MaxConnectionsPerServer propriété. Par exemple, si MaxConnectionsPerServer = 2, alors une connexion est réservée comme connexion principale, et une seconde connexion est utilisée comme supplémentaire pour les opérations exécutées dans d’autres fils. En conséquence, si MaxConnectionsPerServer = 3, alors la première connexion est réservée comme connexion principale, et deux autres connexions sont utilisées comme supplémentaires pour les opérations exécutées dans d’autres fils. Lorsqu’une demande de connexion provient d’un nouveau fil et que toutes les connexions sont déjà utilisées, le client attend que le nombre de connexions utilisées diminue. C’est un moment très important qui explique pourquoi la libération correcte des connexions est si importante.

Exemples d’utilisation des clients de messagerie dans un environnement multithread

Un utilisateur peut exécuter des opérations dans différents fils de plusieurs manières. Elles peuvent être réparties en deux types.

Utilisation des méthodes asynchrones (Begin/End)

Un utilisateur utilise les méthodes asynchrones (Begin/End) définies dans le client. Dans ce cas, le client de messagerie lance de nouveaux fils quand cela est nécessaire. Une file de tâches est implémentée dans le client (ne pas confondre avec la file de commandes de la connexion). Une tâche peut être exécutée si une connexion est disponible. Une fois que le nombre de connexions utilisées devient inférieur à la valeur limite, le client crée une nouvelle connexion, crée un fil pour la tâche en cours et exécute cette tâche. Exemple d’utilisation d’opérations asynchrones :

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

Utilisation de fils créés par l’utilisateur

Un utilisateur peut créer des fils en utilisant des objets tels que Thread, ThreadPool, Task, ou tout autre objet destiné à cet usage. Un utilisateur peut également utiliser des fils créés dans du code tiers. Dans ce cas, le client possède deux modèles de comportement.

a. Si l’utilisateur ne s’est pas chargé de créer des connexions supplémentaires pour les opérations dans le fil, toutes les opérations de ce fil seront envoyées à la file d’attente de commandes de la connexion principale. Voici un exemple d’opérations dans un fil supplémentaire sans créer de nouvelle connexion — toutes les transactions se font via la connexion principale :

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. Lorsque l’utilisateur exécute une méthode pour créer une nouvelle connexion pour un fil supplémentaire, ce fil est bloqué jusqu’à ce que la valeur du quota pour les nouvelles connexions change afin de permettre une nouvelle connexion. Alors une nouvelle connexion est créée. Cette connexion est définie comme connexion par défaut pour toutes les opérations dans ce fil. Après que toutes les opérations de ce fil soient terminées, la connexion doit être supprimée. Pour créer de nouvelles connexions, utilisez le CredentialsByHostClient.CreateConnection méthode. Cette méthode renvoie un objet qui implémente le IDisposable interface. Pour libérer la connexion, le Dispose méthode doit être invoquée. Créer et supprimer une connexion doit être exécuté à l’intérieur du fil où les opérations de messagerie sont exécutées. Une tentative de créer une nouvelle connexion dans le fil où le client de messagerie a été créé entraîne une erreur, car ce fil ne peut pas être utilisé à ce moment‑là pour créer une nouvelle connexion. Créer une nouvelle connexion n’est également pas possible lorsque MaxConnectionsPerServer = 1. Exemple de code créant une nouvelle connexion dans un fil supplémentaire :

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 de connexion

À partir d’Aspose.Email 19.3, le pool de connexions a été refactorisé. Le EmailClient classe a été introduite, ce qui remplace finalement le CredentialsByHostClient classe. Le EmailClient la classe fournit un ConnectionAsgmtMode propriété qui définit le mode d’allocation des connexions dans un environnement multithread. EmailClient.ConnectionAsgmtMode est défini à l’aide du ConnectionAsgmtType énumération.

Types de connexion

Il existe trois types de connexion :

  • La connexion principale. Il s’agit de la connexion créée et supprimée avec le client de messagerie. Elle ne peut pas être créée ou supprimée manuellement.
  • Connexion par défaut. Un utilisateur peut créer des connexions par défaut pour les fils avec le CreateConnection méthode. Si une connexion par défaut existe, toutes les méthodes du client de messagerie exécutées dans un fil utiliseront implicitement cette connexion. Une seule connexion par défaut peut exister par fil. Elle peut être créée manuellement ou automatiquement, selon le EmailClient.ConnectionAsgmtMode propriété. Ces connexions peuvent être créées manuellement avec le EmailClient.CreateConnection(createAsDefaultConnection = true) méthode. Si aucune connexion par défaut n’est utilisée (en fonction du mode d’allocation de connexion), la connexion principale est utilisée implicitement à la place.
  • Connexions indépendantes. Ce sont des connexions qui ne sont pas liées aux fils. Elles peuvent être créées manuellement et doivent être utilisées explicitement comme paramètre de méthode. Ces connexions peuvent être créées manuellement avec le EmailClient.CreateConnection() méthode ou le EmailClient.CreateConnection(createAsDefaultConnection = false) méthode.

Types d’allocation de connexion

Pour configurer le EmailClient.ConnectionAsgmtMode propriété, le ConnectionAsgmtType l’énumération est utilisée. Les types d’allocation qu’elle fournit sont listés ci‑dessous.

  • ConnectionAsgmtType.UseMainOrDefault Ce mode est utilisé par défaut dans les clients de messagerie. Le client utilise la connexion principale pour toutes les opérations provenant de plusieurs fils si aucune connexion par défaut n’a été créée, ou si aucune connexion n’a été passée explicitement comme paramètre de méthode. La connexion principale est créée en même temps que le client de messagerie. L’utilisateur peut créer des connexions par défaut pour les fils avec le CreateConnection méthode. Si une connexion par défaut pour un fil est créée, elle est utilisée implicitement pour toutes les méthodes du client de messagerie invoquées dans ce fil. Si aucune connexion par défaut pour un fil n’est créée, la connexion principale est utilisée pour toutes les méthodes invoquées dans ce fil. L’utilisateur peut également créer des connexions non liées aux fils (pas de connexions par défaut) avec le CreateConnection méthode. Pour utiliser d’autres connexions (ni principale ni par défaut), l’utilisateur doit transmettre explicitement la connexion comme paramètre de la méthode. L’utilisateur peut également créer un nombre quelconque de connexions. Une seule connexion par défaut peut exister par fil. Veuillez noter que les connexions par défaut fonctionnent correctement si l’utilisateur utilise Thread objets pour la programmation multitâche. Si l’utilisateur utilise un pool de connexions ou Task objets pour le multitâche, ce mode peut conduire à un comportement incorrect. Pour éviter ce problème, l’utilisateur doit supprimer manuellement la connexion par défaut (si elle est utilisée) à la fin de l’exécution du code.

  • ConnectionAsgmtType.UseMain Le client de messagerie utilise la connexion principale pour toutes les opérations provenant de plusieurs fils. La connexion principale est créée en même temps que le client de messagerie. L’utilisateur ne peut pas créer de connexions par défaut, mais peut créer des connexions non liées aux fils avec le CreateConnection méthode. Pour utiliser d’autres connexions, l’utilisateur doit les transmettre explicitement comme paramètre de méthode.

  • ConnectionAsgmtType.UseDefault Le client de messagerie utilise uniquement les connexions par défaut implicitement pour toutes les opérations provenant de plusieurs fils. La connexion principale n’est pas utilisée dans ce mode. Si aucune connexion par défaut n’a été créée pour un fil (à la première invocation d’une méthode du client de messagerie), le client crée implicitement une connexion par défaut pour le fil avant que la première opération ne soit exécutée. L’utilisateur ne peut pas créer de connexions par défaut pour les fils avec le CreateConnection méthode car elles sont créées automatiquement. L’utilisateur peut également créer des connexions non liées aux fils avec le CreateConnection méthode. Pour utiliser d’autres connexions, l’utilisateur doit les transmettre explicitement comme paramètre de méthode. L’utilisateur peut également créer un nombre quelconque de connexions. Une seule connexion par défaut peut être utilisée par fil. Veuillez noter que les connexions par défaut fonctionnent correctement si l’utilisateur utilise Thread objets pour la programmation multitâche. Si l’utilisateur utilise un pool de connexions ou Task objets pour le multitâche, ce mode peut conduire à un comportement incorrect. Pour éviter ce problème, l’utilisateur doit supprimer manuellement la connexion par défaut à la fin de l’exécution du code.

Recommandations

Si l’utilisateur envoie toutes les commandes sur la connexion principale, une situation peut survenir où les commandes de différents fils sont mélangées. L’utilisateur doit comprendre quelles commandes dépendent de leur séquence et prendre des mesures pour synchroniser ces commandes. Il faut également envisager la possibilité d’exécuter des commandes dans différentes sessions (IMAP/POP3). Les opérations les plus longues sont FetchMessage, AppendMessage, et Send. Il est probablement judicieux d’effectuer ces opérations avec un nouveau fil et une nouvelle connexion. Les opérations rapides telles que Delete il est logique de les exécuter avec la connexion principale. Veuillez noter que l’initialisation d’une nouvelle connexion est une opération assez consommatrice de temps.