Supporto multi-threading nei client di posta
I client di posta come ImapClient e Pop3Client consentono agli utenti di usarli in ambiente multithreading. I client di posta permettono di avere una o più connessioni con un server. Per gestire un insieme di connessioni all’interno dei client si utilizza un pool di connessioni. Il numero di connessioni che possono essere create e utilizzate contemporaneamente è limitato dalla proprietà CredentialsByHostClient.MaxConnectionsPerServer. Questa proprietà può essere impostata a 1 o a un valore maggiore. Per impostazione predefinita il valore è 10. È stata implementata una coda di comandi per la connessione per supportare operazioni multithread. I comandi implementano le operazioni più semplici definite nel protocollo, come Noop, Authenticate e così via. L’utente può avviare l’esecuzione di un numero di comandi superiore al numero di connessioni disponibili, ma verranno eseguiti solo quando il client sarà in grado di creare una connessione per l’operazione.
Metodi di utilizzo dei client di posta in ambiente multithreading
I client di posta hanno il seguente comportamento:
1. Nel caso in cui MaxConnectionsPerServer = 1, il client crea 1 connessione, esegue l’autenticazione e l’autorizzazione. Questa connessione rimane attiva fino al momento in cui il client verrà eliminato. Tutte le operazioni da thread diversi sono indirizzate a una singola coda di comandi posta nella connessione principale.
2. Nel caso in cui MaxConnectionsPerServer > 1, il client crea la quantità necessaria di connessioni, esegue l’autenticazione e l’autorizzazione per ogni connessione. Una connessione è riservata come connessione principale. Questa connessione rimane attiva fino al momento in cui il client viene eliminato. Tutte le altre connessioni vengono create e eliminate su richiesta. La quantità massima di tali connessioni è definita dalla proprietà MaxConnectionsPerServer, ad esempio se MaxConnectionsPerServer = 2, una è riservata come connessione principale e la seconda viene usata come aggiuntiva per le operazioni eseguite in altri thread. Analogamente, se MaxConnectionsPerServer = 3, la prima connessione è riservata come principale e le altre due sono usate come aggiuntive per le operazioni in altri thread. Nel caso in cui arrivi una nuova richiesta di connessione da un nuovo thread e tutte le connessioni sono già in uso, il client attende finché il numero di connessioni utilizzate non diminuisce. Questo è un momento molto importante che chiarisce perché il corretto rilascio delle connessioni è così fondamentale.
Esempi
L’utente può eseguire operazioni in diversi thread in vari modi. Possono essere divisi in due tipologie:
1. L’utente utilizza i metodi asincroni (Begin/End) definiti nel client. In questo caso il client di posta avvia nuovi thread quando necessario. Nel client è implementata una coda di attività (per favore, non confonderla con la coda dei comandi nella connessione). Un’attività può essere eseguita se la connessione è disponibile. Quando la quantità di connessioni utilizzate diventa inferiore al valore limite, il client crea una nuova connessione, crea un thread per l’attività corrente e la esegue. Esempio di utilizzo di operazioni asincrone:
2. L’utente può creare thread utilizzando oggetti come Thread, ThreadPool, Task o qualsiasi altro oggetto previsto a questo scopo. Inoltre, l’utente può utilizzare thread creati da codice di terze parti. In questo caso il client ha due modelli di comportamento
a. Se l’utente non ha creato connessioni aggiuntive per le operazioni nel thread, tutte le operazioni per questo thread verranno inviate alla coda dei comandi della connessione principale. Un esempio di operazioni in un thread aggiuntivo senza creare una nuova connessione, tutte le transazioni avvengono tramite la connessione principale:
b. Quando l’utente esegue il metodo per creare una nuova connessione per un thread aggiuntivo, questo thread è bloccato finché il valore di quota per le nuove connessioni non viene modificato per consentire una nuova connessione. Successivamente viene creata la 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 utilizzare il metodo CredentialsByHostClient.CreateConnection. Questo metodo restituisce un oggetto che implementa l’interfaccia IDisposable. Per rilasciare la connessione è necessario invocare il metodo Dispose. La creazione e l’eliminazione della connessione devono essere eseguite all’interno del thread in cui vengono eseguite le operazioni di posta. Tentare di creare una nuova connessione nel thread in cui è stato creato il client di posta porta a un errore, poiché questo thread non può essere utilizzato per la creazione di una nuova connessione. Inoltre, la creazione di una nuova connessione non è possibile quando MaxConnectionsPerServer = 1. Un esempio di codice per creare una nuova connessione in un thread aggiuntivo:
Raccomandazioni
È importante notare che 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 adottare misure per sincronizzare tali comandi. È inoltre necessario considerare la possibilità di eseguire i comandi in sessioni diverse (IMAP/POP3). Le operazioni più dispendiose in termini di tempo, come FetchMessage, AppendMessage e Send, probabilmente hanno senso essere eseguite con un nuovo thread e una nuova connessione. Tuttavia, operazioni rapide come Delete hanno senso essere eseguite con la connessione principale. Si noti che l’inizializzazione di una nuova connessione è un’operazione sufficientemente dispendiosa in termini di tempo.