Obsługa wielowątkowości i pula połączeń w klientach poczty
Klienci poczty tacy jak ImapClient, Pop3Client, oraz SmtpClient może być używana w środowisku wielowątkowym. Klient może utrzymywać jedno lub więcej połączeń z serwerem. Do zarządzania zestawem połączeń w kliencie używana jest pula połączeń. Liczba połączeń, które mogą być jednocześnie tworzone i używane, jest ograniczona przez CredentialsByHostClient.MaxConnectionsPerServer właściwość. Ta właściwość może być ustawiona na 1 lub większą wartość. Domyślnie wynosi 10.
Kolejka poleceń jest implementowana dla każdego połączenia w celu obsługi operacji wielowątkowych. Polecenia implementują najprostsze operacje zdefiniowane w protokole, takie jak Noop, Authenticate, i tak dalej. Użytkownik może rozpocząć wykonywanie więcej poleceń niż dostępnych połączeń, ale zostaną one wykonane dopiero, gdy klient będzie w stanie utworzyć połączenie dla danej operacji.
Jak klienci poczty zachowują się w środowisku wielowątkowym
Klienci poczty mają następujące zachowanie:
-
Kiedy
MaxConnectionsPerServer = 1, klient tworzy jedno połączenie i wykonuje uwierzytelnianie oraz autoryzację. To połączenie jest utrzymywane w stanie działającym do momentu zwolnienia klienta. Wszystkie operacje z różnych wątków są kierowane do jednej kolejki poleceń umieszczonej w głównym połączeniu. -
Kiedy
MaxConnectionsPerServer > 1, klient tworzy wymaganą liczbę połączeń i wykonuje uwierzytelnianie oraz autoryzację dla każdego połączenia. Jedno połączenie jest zarezerwowane jako główne. To połączenie jest utrzymywane w stanie działającym do momentu zwolnienia klienta. Wszystkie pozostałe połączenia są tworzone i zwalniane na żądanie. Maksymalna liczba takich połączeń jest definiowana przezMaxConnectionsPerServerwłaściwość. Na przykład, jeśliMaxConnectionsPerServer = 2, wtedy jedno połączenie jest zarezerwowane jako główne, a drugie połączenie jako dodatkowe do operacji wykonywanych w innych wątkach. Odpowiednio, jeśliMaxConnectionsPerServer = 3, wtedy pierwsze połączenie jest zarezerwowane jako główne, a dwa inne połączenia są używane jako dodatkowe do operacji wykonywanych w innych wątkach. Gdy przychodzi żądanie połączenia z nowego wątku i wszystkie połączenia są już używane, klient czeka, aż liczba używanych połączeń zmaleje. To bardzo ważny moment, który wyjaśnia, dlaczego prawidłowe zwalnianie połączeń jest tak istotne.
Przykłady użycia klientów poczty w środowisku wielowątkowym
Użytkownik może wykonywać operacje w różnych wątkach na kilka sposobów. Można je podzielić na dwa typy.
Używanie asynchronicznych metod (Begin/End)
Użytkownik korzysta z asynchronicznych metod (Begin/End) zdefiniowanych w kliencie. W tym przypadku klient poczty uruchamia nowe wątki w razie potrzeby. W kliencie zaimplementowano kolejkę zadań (nie mylić z kolejką poleceń w połączeniu). Zadanie może zostać wykonane, jeśli dostępne jest połączenie. Gdy liczba używanych połączeń spadnie poniżej limitu, klient tworzy nowe połączenie, tworzy wątek dla bieżącego zadania i wykonuje to zadanie. Przykład użycia operacji asynchronicznych:
// 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);
Używanie wątków tworzonych przez użytkownika
Użytkownik może tworzyć wątki przy użyciu obiektów takich jak Thread, ThreadPool, Task, lub dowolne inne obiekty przeznaczone do tego celu. Użytkownik może także używać wątków tworzonych w kodzie zewnętrznym. W takim przypadku klient ma dwa modele zachowania.
a. Jeśli użytkownik nie zadbał o tworzenie dodatkowych połączeń dla operacji w wątku, wszystkie operacje dla tego wątku będą wysyłane do kolejki poleceń głównego połączenia. Poniżej przykład operacji w dodatkowym wątku bez tworzenia nowego połączenia — wszystkie transakcje są realizowane przez główne połączenie:
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. Gdy użytkownik uruchamia metodę tworzącą nowe połączenie dla dodatkowego wątku, wątek jest blokowany, aż wartość kwoty dla nowych połączeń zmieni się, aby zezwolić na nowe połączenie. Następnie nowe połączenie jest tworzone. To połączenie jest ustawiane jako domyślne połączenie dla wszystkich operacji w tym wątku. Po zakończeniu wszystkich operacji w tym wątku połączenie musi zostać zwolnione. Aby tworzyć nowe połączenia, użyj CredentialsByHostClient.CreateConnection metoda. Ta metoda zwraca obiekt implementujący IDisposable interfejs. Aby zwolnić połączenie, należy użyć Dispose metoda musi zostać wywołana. Tworzenie i zwalnianie połączenia musi być wykonywane wewnątrz wątku, w którym wykonywane są operacje pocztowe. Próba utworzenia nowego połączenia w wątku, w którym został utworzony klient poczty, prowadzi do błędu, ponieważ ten wątek nie może w tym momencie tworzyć nowego połączenia. Tworzenie nowego połączenia nie jest także możliwe, gdy MaxConnectionsPerServer = 1. Przykład kodu tworzącego nowe połączenie w dodatkowym wątku:
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));
}
});
Pula połączeń
Od wersji Aspose.Email 19.3 pula połączeń została przepisana. EmailClient klasa została wprowadzona, co ostatecznie zastępuje CredentialsByHostClient klasa. Ta EmailClient klasa zapewnia ConnectionAsgmtMode właściwości, która określa tryb alokacji połączeń w środowisku wielowątkowym. EmailClient.ConnectionAsgmtMode jest ustawiane przy użyciu ConnectionAsgmtType wyliczanka.
Typy połączeń
Istnieją trzy typy połączeń:
- Główne połączenie. To połączenie jest tworzone i zwalniane razem z klientem poczty. Nie może być tworzone ani zwalniane ręcznie.
- Domyślne połączenie. Użytkownik może tworzyć domyślne połączenia dla wątków przy użyciu CreateConnection metoda. Jeśli istnieje domyślne połączenie, wszystkie metody klienta poczty wykonywane w wątku będą implicite używać tego połączenia. Na wątek może istnieć tylko jedno domyślne połączenie. Może być ono tworzone ręcznie lub automatycznie, w zależności od
EmailClient.ConnectionAsgmtModewłaściwość. Te połączenia mogą być tworzone ręcznie przy pomocyEmailClient.CreateConnection(createAsDefaultConnection = true)metoda. Jeśli domyślne połączenie nie jest używane (zależy od trybu alokacji połączeń), zamiast tego używane jest implicite główne połączenie. - Niezależne połączenia. Są to połączenia niepowiązane z wątkami. Mogą być tworzone ręcznie i muszą być używane explicite jako parametr metody. Te połączenia mogą być tworzone ręcznie przy pomocy
EmailClient.CreateConnection()metoda lubEmailClient.CreateConnection(createAsDefaultConnection = false)metoda.
Typy alokacji połączeń
Aby skonfigurować EmailClient.ConnectionAsgmtMode właściwość, ConnectionAsgmtType wyliczenia jest używane. Poniżej wymieniono dostępne typy alokacji.
-
ConnectionAsgmtType.UseMainOrDefault Ten tryb jest domyślnie używany w klientach poczty. Klient poczty używa głównego połączenia dla wszystkich operacji z wielu wątków, jeśli nie utworzono domyślnego połączenia lub nie przekazano połączenia explicite jako parametru metody. Główne połączenie jest tworzone w tym samym czasie co klient poczty. Użytkownik może tworzyć domyślne połączenia dla wątków przy użyciu
CreateConnectionmetoda. Jeśli domyślne połączenie dla wątku zostanie utworzone, jest ono używane implicite dla wszystkich metod klienta poczty wywoływanych w tym wątku. Jeśli domyślne połączenie dla wątku nie zostanie utworzone, główne połączenie jest używane dla wszystkich metod wywoływanych w tym wątku. Użytkownik może także tworzyć połączenia niepowiązane z wątkami (nie domyślne połączenia) przy pomocyCreateConnectionmetoda. Aby używać innych połączeń (nie głównych i nie domyślnych), użytkownik musi przekazać połączenie explicite jako parametr metody. Użytkownik może dodatkowo tworzyć dowolną liczbę połączeń. Na wątek może istnieć tylko jedno domyślne połączenie. Należy zauważyć, że domyślne połączenia działają poprawnie, jeśli użytkownik używaThreadobiekty do programowania wielozadaniowego. Jeśli użytkownik używa puli połączeń lubTaskobiekty do wielozadaniowości, ten tryb może prowadzić do nieprawidłowego zachowania. Aby uniknąć tego problemu, użytkownik musi ręcznie zwolnić domyślne połączenie (jeśli jest używane) po zakończeniu wykonywania kodu. -
ConnectionAsgmtType.UseMain Klient poczty używa głównego połączenia dla wszystkich operacji z wielu wątków. Główne połączenie jest tworzone w tym samym czasie co klient poczty. Użytkownik nie może tworzyć domyślnych połączeń, ale może tworzyć połączenia niepowiązane z wątkami przy użyciu
CreateConnectionmetoda. Aby używać innych połączeń, użytkownik musi przekazać je explicite jako parametr metody. -
ConnectionAsgmtType.UseDefault Klient poczty używa domyślnych połączeń domyślnie dla wszystkich operacji z wielu wątków. Główne połączenie nie jest używane w tym trybie. Jeśli dla wątku nie zostało utworzone domyślne połączenie (przy pierwszym wywołaniu metody klienta poczty), klient poczty tworzy domyślne połączenie implicite dla wątku przed wykonaniem pierwszej operacji. Użytkownik nie może tworzyć domyślnych połączeń dla wątków przy użyciu
CreateConnectionmetoda, ponieważ są tworzone automatycznie. Użytkownik może także tworzyć połączenia niepowiązane z wątkami przy pomocyCreateConnectionmetoda. Aby używać innych połączeń, użytkownik musi przekazać je explicite jako parametr metody. Użytkownik może dodatkowo tworzyć dowolną liczbę połączeń. Na wątek może być używane tylko jedno domyślne połączenie. Należy zauważyć, że domyślne połączenia działają poprawnie, jeśli użytkownik używaThreadobiekty do programowania wielozadaniowego. Jeśli użytkownik używa puli połączeń lubTaskobiekty do wielozadaniowości, ten tryb może prowadzić do nieprawidłowego zachowania. Aby uniknąć tego problemu, użytkownik musi ręcznie zwolnić domyślne połączenie po zakończeniu wykonywania kodu.
Zalecenia
Jeśli użytkownik wysyła wszystkie polecenia do głównego połączenia, może dojść do sytuacji, w której polecenia z różnych wątków zostaną pomieszane. Użytkownik powinien rozumieć, które polecenia zależą od kolejności i podjąć środki synchronizujące takie polecenia. Należy również uwzględnić możliwość wykonywania poleceń w różnych sesjach (IMAP/POP3). Najbardziej czasochłonne operacje to FetchMessage, AppendMessage, oraz Send. Prawdopodobnie ma sens wykonywanie tych operacji w nowym wątku i przy nowym połączeniu. Szybkie operacje, takie jak Delete ma sens, aby wykonywać je przy użyciu głównego połączenia. Należy zauważyć, że inicjalizacja nowego połączenia jest dość czasochłonną operacją.