Hỗ trợ đa luồng trong các client email

Các client email như ImapClientPop3Client cho phép người dùng sử dụng chúng trong môi trường đa luồng. Các client email cho phép có một hoặc nhiều kết nối với máy chủ. Để quản lý một tập hợp các kết nối bên trong client, sử dụng pool kết nối. Số lượng kết nối có thể tạo và sử dụng đồng thời bị giới hạn bởi thuộc tính CredentialsByHostClient.MaxConnectionsPerServer. Thuộc tính này có thể đặt thành 1 hoặc giá trị lớn hơn. Mặc định thuộc tính này bằng 10. Hàng đợi lệnh đã được triển khai cho 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 đượ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 số lượng lệnh lớn hơn số lượng kết nối khả dụng. Nhưng các lệnh chỉ được thực thi khi client có thể tạo kết nối cho thao tác đó.

Phương pháp sử dụng client email trong môi trường đa luồng

Các client email có hành vi sau:

1. Khi MaxConnectionsPerServer = 1, client tạo 1 kết nối, thực hiện xác thực và ủy quyền. Kết nối này duy trì ở trạng thái hoạt động 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.

2. Khi MaxConnectionsPerServer > 1, client tạo số lượng kết nối cần thiết, 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 dành làm kết nối chính. Kết nối này được duy trì ở trạng thái hoạt động 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 như vậy được xác định bởi thuộc tính MaxConnectionsPerServer, ví dụ nếu MaxConnectionsPerServer = 2 thì một kết nối được dành làm kết nối chính, và kết nối thứ hai được dùng làm bổ sung cho các thao tác thực hiện trong các luồng khác. Tương tự, nếu MaxConnectionsPerServer = 3 thì kết nối đầu tiên là kết nối chính, và hai kết nối còn lại được dùng làm bổ sung cho các thao tác thực hiện trong các luồng khác. Trong trường hợp có yêu cầu kết nối tiếp theo từ luồng mới, và tất cả các kết nối đã được sử dụng, client sẽ chờ cho đến khi số lượng kết nối đang dùng giảm xuống. Đây là một điểm quan trọng giải thích tại sao việc giải phóng kết nối đúng cách lại rất quan trọng.

Ví dụ

Người dùng có thể thực hiện các hoạt động trong các luồng khác nhau theo một số cách. Chúng có thể được chia thành hai loại:

1. Người dùng sử dụng các phương thức bất đồng bộ (Begin/End) được định nghĩa trong client. Trong trường hợp này client email khởi chạy các luồng mới khi cần thiết. Trong client được triển khai hàng đợi tác vụ (xin đừng nhầm lẫn với hàng đợi lệnh trong kết nối). 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 đã sử dụng giảm xuống dưới giá trị giới hạn, client tạo kết nối mới, tạo luồng cho tác vụ hiện tại và thực thi tác vụ này. Ví dụ về việc sử dụng các hoạt động bất đồng bộ:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.setHost("domain.com");
client.setUsername("username");
client.setPassword("password");
client.selectFolder("InBox");

ImapMessageInfoCollection messages = client.listMessages();
IAsyncResult res1 = client.beginFetchMessage(messages.get_Item(0).getUniqueId());
IAsyncResult res2 = client.beginFetchMessage(messages.get_Item(1).getUniqueId());
MailMessage msg1 = client.endFetchMessage(res1);
MailMessage msg2 = client.endFetchMessage(res2);

2. Người dùng có thể tạo các luồng sử dụng các đối tượng như Thread, ExecutorService hoặc bất kỳ đối tượng nào khác cho mục đích này. Người dùng cũng có thể sử dụng các luồng được tạo trong mã của bên thứ ba. Trong trường hợp này client có hai mô hình hành vi

a. Nếu người dùng không tạo các kết nối bổ sung cho các hoạt động trong luồng, tất cả các hoạt động cho luồng này sẽ được gửi vào hàng đợi lệnh tới kết nối chính. Một ví dụ về các hoạt động trong một luồng phụ mà không tạo kết nối mới, tất cả giao dịch đều được thực hiện qua kết nối chính:

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
private static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        client.selectFolder("folderName");
        ImapMessageInfoCollection messageInfoCol = client.listMessages();
        for (ImapMessageInfo messageInfo : messageInfoCol) {
            list.add(client.fetchMessage(messageInfo.getUniqueId()));
        }
    }
});

b. Khi người dùng gọi phương thức để tạo kết nối mới cho luồng phụ, luồng này bị chặn cho đến khi giá trị hạn ngạch cho các kết nối mới được thay đổi để cho phép kết nối mới. Sau đó kết nối mới được tạo. Kết nối này được đặt làm kết nối mặc định cho tất cả các hoạt động trong luồng này. Khi tất cả các hoạt động trong luồng này hoàn thành, phải giải phóng kết nối. Để tạo kết nối mới, sử dụng phương thức CredentialsByHostClient.CreateConnection. Phương thức này trả về một đối tượng triển khai giao diện IDisposable. Để giải phóng kết nối, phải gọi phương thức Dispose. Việc tạo và giải phóng kết nối phải được thực hiện trong luồng mà các hoạt động mail được thực hiện. Cố gắng tạo kết nối mới trong luồng mà client mail đã được tạo sẽ dẫn đến lỗi, vì luồng này tại thời điểm đó không thể được dùng để tạo kết nối mới. Ngoài ra, việc tạo kết nối mới không khả thi khi MaxConnectionsPerServer = 1. Một ví dụ mã cho việc tạo kết nối mới trong luồng phụ:

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        IConnection connection = client.createConnection();
        try {
            client.selectFolder(connection, "FolderName");
            ImapMessageInfoCollection messageInfoCol = client.listMessages(connection);
            for (ImapMessageInfo messageInfo : messageInfoCol)
                list.add(client.fetchMessage(connection, messageInfo.getUniqueId()));
        } finally {
            connection.dispose();
        }
    }
});

Khuyến nghị

Cần lưu ý rằng 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 nên hiểu lệnh nào phụ thuộc vào thứ tự của chúng và thực hiện các biện pháp đồng bộ cho những lệnh như vậy. Cũng cần xem xét 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, như FetchMessage, AppendMessage và Send, có thể thực hiện bằng luồng mới và kết nối mới. Nhưng các thao tác nhanh như Delete nên thực hiện bằng kết nối chính. Lưu ý rằng việc khởi tạo kết nối mới là thao tác tốn thời gian đáng kể.