Hỗ trợ Đa luồng và Pool Kết nối trong Các Ứng dụng Thư điện tử
Các ứng dụng thư như ImapClient, Pop3Client, và SmtpClient có thể được dùng trong môi trường đa luồng. Một client có thể duy trì một hoặc nhiều kết nối với máy chủ. Để quản lý tập hợp các kết nối bên trong client, một pool kết nối được sử dụng. Số lượng kết nối có thể được tạo và sử dụng đồng thời bị giới hạn bởi CredentialsByHostClient.MaxConnectionsPerServer thuộc tính. Thuộc tính này có thể được đặt thành 1 hoặc lớn hơn. Mặc định, giá trị là 10.
Một hàng đợi lệnh được triển khai cho mỗi kết nối để hỗ trợ các thao tác đa luồng. Các lệnh thực hiện các thao tác đơn giản nhất được định nghĩa trong giao thức, như Noop, Authenticate, v.v. Người dùng có thể bắt đầu thực thi nhiều lệnh hơn số kết nối có sẵn, nhưng chúng sẽ chỉ được thực thi khi client có thể tạo một kết nối cho thao tác đó.
Cách Các Ứng Dụng Thư Điện Tử Hoạt Động trong Môi Trường Đa Luồng
Các client email có hành vi sau:
-
Khi
MaxConnectionsPerServer = 1, client tạo một kết nối và thực hiện xác thực và ủy quyền. Kết nối này được giữ ở trạng thái làm việc cho đến khi client được giải phóng. Tất cả các thao tác từ các luồng khác nhau được đưa vào một hàng đợi lệnh đặt trong kết nối chính. -
Khi
MaxConnectionsPerServer > 1, client sẽ tạo số lượng kết nối cần thiết và thực hiện xác thực và ủy quyền cho mỗi kết nối. Một kết nối được giữ làm kết nối chính. Kết nối này được giữ ở trạng thái làm việc cho đến khi client được giải phóng. Tất cả các kết nối khác được tạo và giải phóng theo yêu cầu. Số lượng tối đa của các kết nối này được xác định bởiMaxConnectionsPerServerthuộc tính. Ví dụ, nếuMaxConnectionsPerServer = 2, sau đó một kết nối được giữ làm kết nối chính, và một kết nối thứ hai được dùng làm bổ sung cho các thao tác trong các luồng khác. Do đó, nếuMaxConnectionsPerServer = 3, sau đó kết nối đầu tiên được giữ làm kết nối chính, và hai kết nối khác được dùng làm bổ sung cho các thao tác thực hiện trong các luồng khác. Khi có yêu cầu kết nối đến từ một luồng mới và mọi kết nối đã được sử dụng, client sẽ chờ cho tới khi số lượng kết nối đang dùng giảm xuống. Đây là một thời điểm rất quan trọng giải thích vì sao việc giải phóng kết nối đúng cách lại quan trọng đến vậy.
Ví dụ về Việc Sử Dụng Các Ứng Dụng Thư Điện Tử trong Môi Trường Đa Luồng
Người dùng có thể thực hiện các thao tác trong các luồng khác nhau bằng một số cách. Chúng có thể được chia thành hai loại.
Sử dụng Các Phương Pháp Bất Đồng Bộ (Begin/End)
Người dùng sử dụng các phương pháp bất đồng bộ (Begin/End) được định nghĩa trong client. Trong trường hợp này, client thư sẽ khởi chạy các luồng mới khi cần thiết. Một hàng đợi tác vụ được triển khai trong client (đừng nhầm lẫn với hàng đợi lệnh trong kết nối). Một tác vụ có thể được thực thi nếu có kết nối khả dụng. Khi số lượng kết nối đang dùng giảm xuống dưới giá trị giới hạn, client sẽ tạo một kết nối mới, tạo một luồng cho tác vụ hiện tại và thực thi tác vụ đó. Một ví dụ về việc sử dụng các thao tác bất đồng bộ:
// 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);
Sử dụng Luồng do Người Dùng Tạo
Người dùng có thể tạo luồng bằng các đối tượng như Thread, ThreadPool, Task, hoặc bất kỳ đối tượng nào khác dành cho mục đích này. Người dùng cũng có thể sử dụng các luồng do mã của bên thứ ba tạo ra. Trong trường hợp này, client có hai mô hình hành vi.
a. Nếu người dùng chưa tạo các kết nối bổ sung cho các thao tác trong luồng, mọi thao tác cho luồng này sẽ được gửi tới hàng đợi lệnh của kết nối chính. Dưới đây là một ví dụ về các thao tác trong một luồng bổ sung mà không tạo kết nối mới — tất cả các giao dịch được thực hiện qua kết nối chính:
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. Khi người dùng chạy một phương thức để tạo kết nối mới cho một luồng bổ sung, luồng này sẽ bị chặn cho tới khi giới hạn quota cho kết nối mới thay đổi cho phép tạo kết nối mới. Sau đó một kết nối mới được tạo. Kết nối này được thiết lập làm kết nối mặc định cho tất cả các thao tác trong luồng này. Sau khi mọi thao tác trong luồng này hoàn thành, kết nối phải được giải phóng. Để tạo kết nối mới, sử dụng CredentialsByHostClient.CreateConnection phương thức. Phương thức này trả về một đối tượng thực hiện IDisposable giao diện. Để giải phóng kết nối, Dispose phương thức phải được gọi. Tạo và giải phóng một kết nối phải được thực hiện bên trong luồng nơi các thao tác thư được thực hiện. Cố gắng tạo một kết nối mới trong luồng nơi client thư được tạo sẽ dẫn đến lỗi, vì luồng này không thể tạo kết nối mới tại thời điểm đó. Việc tạo kết nối mới cũng không thể thực hiện khi MaxConnectionsPerServer = 1. Một ví dụ mã tạo một kết nối mới trong một luồng bổ sung:
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 Kết Nối
Bắt đầu với Aspose.Email 19.3, pool kết nối đã được tái cấu trúc. Các EmailClient lớp đã được giới thiệu, cuối cùng thay thế CredentialsByHostClient lớp. EmailClient lớp cung cấp một ConnectionAsgmtMode thuộc tính xác định chế độ cấp phát kết nối trong môi trường đa luồng. EmailClient.ConnectionAsgmtMode được đặt bằng cách sử dụng ConnectionAsgmtType enumeration.
Các Loại Kết Nối
Có ba loại kết nối:
- Kết nối chính. Đây là kết nối được tạo và giải phóng cùng với ứng dụng thư. Nó không thể được tạo hay giải phóng thủ công.
- Kết nối mặc định. Người dùng có thể tạo kết nối mặc định cho các luồng bằng CreateConnection phương thức. Nếu có một kết nối mặc định, tất cả các phương thức của ứng dụng thư được thực thi trong một luồng sẽ ngầm sử dụng kết nối này. Chỉ một kết nối mặc định có thể tồn tại cho mỗi luồng. Nó có thể được tạo thủ công hoặc tự động, tùy thuộc vào
EmailClient.ConnectionAsgmtModethuộc tính. Các kết nối này có thể được tạo thủ công bằngEmailClient.CreateConnection(createAsDefaultConnection = true)phương thức. Nếu không sử dụng kết nối mặc định (phụ thuộc vào chế độ phân bổ kết nối), kết nối chính sẽ được sử dụng ngầm thay thế. - Kết nối độc lập. Đây là các kết nối không liên kết với luồng. Chúng có thể được tạo thủ công và phải được sử dụng rõ ràng như một tham số phương thức. Các kết nối này có thể được tạo thủ công bằng
EmailClient.CreateConnection()phương thức hoặcEmailClient.CreateConnection(createAsDefaultConnection = false)phương thức.
Các loại cấp phát kết nối
Để thiết lập EmailClient.ConnectionAsgmtMode thuộc tính, ConnectionAsgmtType đánh số được sử dụng. Các kiểu phân bổ nó cung cấp được liệt kê dưới đây.
-
ConnectionAsgmtType.UseMainOrDefault Chế độ này là mặc định trong các ứng dụng thư điện tử. Ứng dụng sử dụng kết nối chính cho tất cả các thao tác từ nhiều luồng nếu chưa có kết nối mặc định, hoặc nếu một kết nối không được truyền rõ ràng như tham số phương thức. Kết nối chính được tạo đồng thời với ứng dụng. Người dùng có thể tạo kết nối mặc định cho các luồng bằng
CreateConnectionphương thức. Nếu một kết nối mặc định cho một luồng được tạo, nó sẽ được sử dụng ngầm cho tất cả các phương thức của ứng dụng thư được gọi trong luồng đó. Nếu không có kết nối mặc định cho luồng, kết nối chính sẽ được dùng cho tất cả các phương thức được gọi trong luồng đó. Người dùng cũng có thể tạo các kết nối không liên kết với luồng (không phải kết nối mặc định) bằngCreateConnectionphương thức. Để sử dụng các kết nối khác (không phải chính và không phải mặc định), người dùng phải truyền kết nối một cách rõ ràng như một tham số của phương thức. Người dùng cũng có thể tạo thêm bất kỳ số lượng kết nối nào. Chỉ một kết nối mặc định có thể tồn tại cho mỗi luồng. Xin lưu ý rằng các kết nối mặc định hoạt động đúng nếu người dùng sử dụngThreadđối tượng cho lập trình đa nhiệm. Nếu người dùng sử dụng một pool kết nối hoặcTaskđối tượng cho đa nhiệm, chế độ này có thể dẫn đến hành vi không chính xác. Để tránh vấn đề này, người dùng phải tự giải phóng kết nối mặc định (nếu nó được sử dụng) vào cuối quá trình thực thi mã. -
ConnectionAsgmtType.UseMain Ứng dụng thư điện tử sử dụng kết nối chính cho tất cả các thao tác từ nhiều luồng. Kết nối chính được tạo đồng thời với ứng dụng thư. Người dùng không thể tạo kết nối mặc định, nhưng có thể tạo các kết nối không liên kết với luồng bằng
CreateConnectionphương thức. Để sử dụng các kết nối khác, người dùng phải truyền chúng một cách rõ ràng như một tham số phương thức. -
ConnectionAsgmtType.UseDefault Ứng dụng thư điện tử chỉ sử dụng các kết nối mặc định một cách ngụ ý cho tất cả các thao tác từ nhiều luồng. Kết nối chính không được sử dụng trong chế độ này. Nếu một kết nối mặc định chưa được tạo cho một luồng (tại lần gọi phương thức đầu tiên của ứng dụng thư), ứng dụng sẽ tạo ngầm một kết nối mặc định cho luồng đó trước khi thực hiện thao tác đầu tiên. Người dùng không thể tạo kết nối mặc định cho luồng bằng
CreateConnectionphương thức vì chúng được tạo tự động. Người dùng cũng có thể tạo các kết nối không liên kết với luồng bằngCreateConnectionphương pháp. Để sử dụng các kết nối khác, người dùng phải truyền chúng một cách rõ ràng như một tham số phương thức. Người dùng cũng có thể tạo thêm bất kỳ số lượng kết nối nào. Chỉ một kết nối mặc định có thể được sử dụng cho mỗi luồng. Xin lưu ý rằng các kết nối mặc định hoạt động đúng nếu người dùng sử dụngThreadđối tượng cho lập trình đa nhiệm. Nếu người dùng sử dụng một pool kết nối hoặcTaskđối tượng cho đa nhiệm, chế độ này có thể dẫn đến hành vi không chính xác. Để tránh vấn đề này, người dùng phải tự động giải phóng kết nối mặc định vào cuối quá trình thực thi mã.
Khuyến nghị
Nếu người dùng gửi tất cả lệnh tới kết nối chính, có thể xảy ra tình huống các lệnh từ các luồng khác nhau bị trộn lẫn. Người dùng cần hiểu lệnh nào phụ thuộc vào thứ tự và thực hiện các biện pháp đồng bộ các lệnh như vậy. Cũng cần cân nhắc khả năng thực thi lệnh trong các phiên khác nhau (IMAP/POP3). Các thao tác tốn thời gian nhất là FetchMessage, AppendMessage, và Send. Có lẽ hợp lý hơn khi thực hiện các thao tác này với một luồng mới và một kết nối mới. Các thao tác nhanh như Delete có ý nghĩa để thực hiện với kết nối chính. Xin lưu ý rằng việc khởi tạo một kết nối mới là một thao tác tốn thời gian đáng kể.