Supporto al multithreading e pool di connessioni nei client di posta

Client di posta come ImapClient, Pop3Client, e SmtpClient può essere usato in un ambiente multithread. Un client può mantenere una o più connessioni con un server. Per gestire l’insieme di connessioni all’interno di un client, viene usato un pool di connessioni. Il numero di connessioni che può essere creato e usato contemporaneamente è limitato dal CredentialsByHostClient.MaxConnectionsPerServer proprietà. Questa proprietà può essere impostata a 1 o a un valore maggiore. Per impostazione predefinita è 10.

Una coda di comandi è implementata per ogni connessione per supportare operazioni multithread. I comandi implementano le operazioni più semplici definite nel protocollo, come Noop, Authenticate, e così via. Un utente può avviare l’esecuzione di più comandi di quanti ce ne siano connessioni disponibili, ma verranno eseguiti solo quando il client sarà in grado di creare una connessione per l’operazione.

Come si Comportano i Client di Posta in un Ambiente Multithread

I client di posta hanno il seguente comportamento:

  1. Quando MaxConnectionsPerServer = 1, il client crea una connessione e esegue l’autenticazione e l’autorizzazione. Questa connessione è mantenuta in stato operativo fino a quando il client non viene eliminato. Tutte le operazioni da thread diversi sono indirizzate a una coda di comandi posta nella connessione principale.

  2. Quando MaxConnectionsPerServer > 1, il client crea il numero necessario di connessioni e esegue l’autenticazione e l’autorizzazione per ogni connessione. Una connessione è riservata come connessione principale. Questa connessione è mantenuta in stato operativo fino a quando il client non viene eliminato. Tutte le altre connessioni sono create e eliminate su richiesta. Il numero massimo di tali connessioni è definito dal MaxConnectionsPerServer proprietà. Per esempio, se MaxConnectionsPerServer = 2, poi una connessione è riservata come connessione principale, e una seconda connessione è usata come aggiuntiva per le operazioni eseguite in altri thread. Di conseguenza, se MaxConnectionsPerServer = 3, poi la prima connessione è riservata come connessione principale, e due altre connessioni sono usate come aggiuntive per le operazioni eseguite in altri thread. Quando una richiesta di connessione arriva da un nuovo thread e tutte le connessioni sono già in uso, il client attende finché il numero di connessioni usate diminuisce. Questo è un momento molto importante che chiarisce perché la corretta eliminazione delle connessioni è così fondamentale.

Esempi di Utilizzo dei Client di Posta in un Ambiente Multithread

Un utente può eseguire operazioni in thread diversi in diversi modi. Possono essere divisi in due tipi.

Utilizzo di Metodi Asincroni (Begin/End)

Un utente utilizza i metodi asincroni (Begin/End) definiti nel client. In questo caso, il client di posta avvia nuovi thread quando necessario. Una coda di task è implementata nel client (da non confondere con la coda dei comandi nella connessione). Un task può essere eseguito se è disponibile una connessione. Una volta che il numero di connessioni usate diventa inferiore al valore limite, il client crea una nuova connessione, crea un thread per il task corrente e lo esegue. Un esempio di utilizzo di operazioni asincrone:

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

Utilizzo di Thread Creati dall’Utente

Un utente può creare thread usando oggetti come Thread, ThreadPool, Task, o qualsiasi altro oggetto destinato a questo scopo. Un utente può anche usare thread creati in codice di terze parti. In questo caso, il client ha due modelli di comportamento.

a. Se l’utente non ha provveduto a creare connessioni aggiuntive per le operazioni nel thread, tutte le operazioni per quel thread saranno inviate alla coda dei comandi della connessione principale. Di seguito è un esempio di operazioni in un thread aggiuntivo senza creare una nuova connessione — tutte le transazioni avvengono tramite la connessione 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. Quando l’utente esegue un metodo per creare una nuova connessione per un thread aggiuntivo, questo thread è bloccato fino a quando il valore di quota per nuove connessioni non cambia per permettere una nuova connessione. Allora viene creata una nuova connessione. Questa connessione è impostata come connessione predefinita per tutte le operazioni in questo thread. Dopo che tutte le operazioni in questo thread sono completate, la connessione deve essere eliminata. Per creare nuove connessioni, usare il CredentialsByHostClient.CreateConnection metodo. Questo metodo restituisce un oggetto che implementa il IDisposable interfaccia. Per rilasciare la connessione, il Dispose il metodo deve essere invocato. Creare ed eliminare una connessione deve essere eseguito all’interno del thread dove le operazioni di posta sono eseguite. Un tentativo di creare una nuova connessione nel thread dove è stato creato il client di posta porta a un errore, poiché quel thread non può essere usato per creare una nuova connessione in quel momento. Creare una nuova connessione non è possibile quando MaxConnectionsPerServer = 1. Un esempio di codice per creare una nuova connessione in un thread aggiuntivo:

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 di Connessione

A partire da Aspose.Email 19.3, il pool di connessioni è stato ristrutturato. Il EmailClient è stata introdotta una classe, che alla fine sostituisce il CredentialsByHostClient classe. Il EmailClient la classe fornisce un ConnectionAsgmtMode proprietà che definisce il modo di allocazione della connessione in un ambiente multithread. EmailClient.ConnectionAsgmtMode è impostato usando il ConnectionAsgmtType enumerazione.

Tipi di connessione

Ci sono tre tipi di connessione:

  • La connessione principale. Questa è la connessione creata e eliminata insieme al client di posta. Non può essere creata o eliminata manualmente.
  • Connessione predefinita. Un utente può creare connessioni predefinite per i thread con il CreateConnection metodo. Se esiste una connessione predefinita, tutti i metodi del client di posta eseguiti in un thread useranno implicitamente questa connessione. Solo una connessione predefinita può esistere per thread. Può essere creata manualmente o automaticamente, a seconda del EmailClient.ConnectionAsgmtMode proprietà. Queste connessioni possono essere create manualmente con il EmailClient.CreateConnection(createAsDefaultConnection = true) metodo. Se non viene usata una connessione predefinita (dipende dalla modalità di allocazione della connessione), la connessione principale è usata implicitamente al suo posto.
  • Connessioni indipendenti. Queste sono connessioni che non sono collegate a thread. Possono essere create manualmente e devono essere usate esplicitamente come parametro del metodo. Queste connessioni possono essere create manualmente con il EmailClient.CreateConnection() metodo o il EmailClient.CreateConnection(createAsDefaultConnection = false) metodo.

Tipi di allocazione della connessione

Per configurare il EmailClient.ConnectionAsgmtMode proprietà, il ConnectionAsgmtType l’enumerazione è usata. I tipi di allocazione forniti sono elencati di seguito.

  • ConnectionAsgmtType.UseMainOrDefault Questa modalità è usata di default nei client di posta. Il client di posta utilizza la connessione principale per tutte le operazioni da più thread se non è stata creata una connessione predefinita, o se una connessione non è stata passata esplicitamente come parametro del metodo. La connessione principale è creata allo stesso tempo del client di posta. L’utente può creare connessioni predefinite per i thread con il CreateConnection metodo. Se viene creata una connessione predefinita per un thread, essa è usata implicitamente per tutti i metodi del client di posta invocati in quel thread. Se non viene creata una connessione predefinita per un thread, la connessione principale è usata per tutti i metodi invocati in quel thread. L’utente può anche creare connessioni non collegate a thread (non predefinite) con il CreateConnection metodo. Per usare altre connessioni (non principali e non predefinite), l’utente deve passare la connessione esplicitamente come parametro del metodo. L’utente può inoltre creare un numero qualsiasi di connessioni. Solo una connessione predefinita può esistere per thread. Si noti che le connessioni predefinite funzionano correttamente se l’utente usa Thread oggetti per la programmazione multitasking. Se l’utente utilizza un pool di connessioni o Task oggetti per il multitasking, questa modalità può portare a un comportamento errato. Per evitare questo problema, l’utente deve eliminare manualmente la connessione predefinita (se è usata) al termine dell’esecuzione del codice.

  • ConnectionAsgmtType.UseMain Il client di posta utilizza la connessione principale per tutte le operazioni da più thread. La connessione principale è creata allo stesso tempo del client di posta. L’utente non può creare connessioni predefinite, ma può creare connessioni non collegate a thread con il CreateConnection metodo. Per utilizzare altre connessioni, l’utente deve passarle esplicitamente come parametro del metodo.

  • ConnectionAsgmtType.UseDefault Il client di posta utilizza solo le connessioni predefinite implicitamente per tutte le operazioni da più thread. La connessione principale non è usata in questa modalità. Se non è stata creata una connessione predefinita per un thread (alla prima invocazione di un metodo del client di posta), il client di posta crea implicitamente una connessione predefinita per il thread prima che venga eseguita la prima operazione. L’utente non può creare connessioni predefinite per i thread con il CreateConnection metodo perché vengono create automaticamente. L’utente può anche creare connessioni non collegate a thread con il CreateConnection metodo. Per utilizzare altre connessioni, l’utente deve passarle esplicitamente come parametro del metodo. L’utente può inoltre creare un numero qualsiasi di connessioni. Solo una connessione predefinita può essere usata per thread. Si noti che le connessioni predefinite funzionano correttamente se l’utente usa Thread oggetti per la programmazione multitasking. Se l’utente utilizza un pool di connessioni o Task oggetti per il multitasking, questa modalità può portare a un comportamento errato. Per evitare questo problema, l’utente deve eliminare manualmente la connessione predefinita al termine dell’esecuzione del codice.

Raccomandazioni

Se l’utente invia tutti i comandi alla connessione principale, può verificarsi una situazione in cui i comandi provenienti da thread diversi si mescolano. L’utente deve capire quali comandi dipendono dalla loro sequenza e prendere misure per sincronizzare tali comandi. È inoltre necessario considerare la possibilità di eseguire comandi in sessioni diverse (IMAP/POP3). Le operazioni più dispendiose sono FetchMessage, AppendMessage, e Send. Probabilmente è opportuno eseguire queste operazioni con un nuovo thread e una nuova connessione. Operazioni rapide come Delete ha senso eseguirlo con la connessione principale. Si noti che l’inizializzazione di una nuova connessione è un’operazione piuttosto dispendiosa in termini di tempo.