Dukungan multi-threading pada klien email
Klien email seperti ImapClient dan Pop3Client Mengizinkan pengguna menggunakannya dalam lingkungan multi-threading. Klien email memungkinkan memiliki satu atau lebih koneksi dengan server. Untuk mengelola sekumpulan koneksi di dalam klien, digunakan connection pool. Jumlah koneksi yang dapat dibuat dan digunakan secara bersamaan dibatasi oleh properti CredentialsByHostClient.MaxConnectionsPerServer. Properti ini dapat diatur menjadi 1 atau nilai yang lebih besar. Secara default properti ini bernilai 10. Antrian perintah telah diimplementasikan untuk koneksi guna mendukung operasi multi-threading. Perintah-perintah mengimplementasikan operasi sederhana yang didefinisikan dalam protokol, seperti Noop, Authenticate, dan sebagainya. Pengguna dapat memulai eksekusi jumlah perintah yang lebih besar daripada jumlah koneksi yang tersedia. Namun perintah akan dijalankan hanya ketika klien dapat membuat koneksi untuk operasi tersebut.
Metode Menggunakan Klien Email dalam Lingkungan Multi-Threading
Klien email memiliki perilaku berikut:
1. Jika MaxConnectionsPerServer = 1, klien membuat 1 koneksi, melakukan autentikasi dan otorisasi. Koneksi ini dipertahankan dalam keadaan kerja sampai klien dibuang. Semua operasi dari thread yang berbeda diarahkan ke satu antrian perintah yang ditempatkan pada koneksi utama.
2. Jika MaxConnectionsPerServer > 1, klien membuat jumlah koneksi yang diperlukan, melakukan autentikasi dan otorisasi untuk setiap koneksi. Satu koneksi dicadangkan sebagai koneksi utama. Koneksi ini dipertahankan dalam keadaan kerja sampai klien dibuang. Semua koneksi lain dibuat dan dibuang sesuai permintaan. Jumlah maksimal koneksi tersebut ditentukan oleh properti MaxConnectionsPerServer, misalnya jika MaxConnectionsPerServer = 2 maka satu koneksi dicadangkan sebagai koneksi utama, dan koneksi kedua digunakan sebagai tambahan untuk operasi yang dijalankan di thread lain. Demikian pula, jika MaxConnectionsPerServer = 3 maka koneksi pertama dicadangkan sebagai koneksi utama, dan dua koneksi lainnya digunakan sebagai tambahan untuk operasi yang dijalankan di thread lain. Jika ada permintaan koneksi berikutnya dari thread baru, dan semua koneksi sudah digunakan, klien akan menunggu hingga jumlah koneksi yang digunakan berkurang. Ini adalah momen penting yang menjelaskan mengapa pembuangan koneksi yang tepat sangat penting.
Contoh
Pengguna dapat mengeksekusi operasi di thread yang berbeda dengan beberapa cara. Mereka dapat dibagi menjadi dua tipe:
1. Pengguna menggunakan metode asynchronous (Begin/End) yang didefinisikan dalam klien. Dalam hal ini klien email meluncurkan thread baru bila diperlukan. Di dalam klien diterapkan antrian tugas (harap tidak bingung dengan antrian perintah pada koneksi). Tugas dapat dijalankan jika koneksi tersedia. Setelah jumlah koneksi yang digunakan menjadi kurang dari nilai batas, klien membuat koneksi baru, membuat thread untuk tugas saat ini, dan mengeksekusi tugas tersebut. Contoh penggunaan operasi asynchronous:
// 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. Pengguna dapat membuat thread menggunakan objek seperti Thread, ExecutorService, atau objek lain yang dimaksudkan untuk tujuan ini. Pengguna juga dapat menggunakan thread yang dibuat dalam kode pihak ketiga. Selama ini klien memiliki dua model perilaku
a. Jika pengguna tidak membuat koneksi tambahan untuk operasi di thread, semua operasi untuk thread ini akan dikirim ke antrian perintah pada koneksi utama. Contoh operasi pada thread tambahan tanpa membuat koneksi baru, semua transaksi dilakukan melalui koneksi utama:
/**
* 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. Ketika pengguna menjalankan metode untuk membuat koneksi baru untuk thread tambahan, thread ini akan diblokir hingga nilai kuota untuk koneksi baru diubah sehingga mengizinkan koneksi baru. Setelah itu koneksi baru dibuat. Koneksi ini ditetapkan sebagai koneksi default untuk semua operasi di thread ini. Setelah semua operasi di thread ini selesai, koneksi harus dibuang. Untuk membuat koneksi baru gunakan metode CredentialsByHostClient.CreateConnection. Metode ini mengembalikan objek yang mengimplementasikan antarmuka IDisposable. Untuk melepaskan koneksi, metode Dispose harus dipanggil. Pembuatan dan pembongkaran koneksi harus dilakukan di dalam thread tempat operasi email dijalankan. Upaya membuat koneksi baru di thread tempat klien email dibuat akan menghasilkan error, karena thread tersebut pada saat ini tidak dapat digunakan untuk membuat koneksi baru. Selain itu, pembuatan koneksi baru tidak memungkinkan ketika MaxConnectionsPerServer = 1. Contoh kode untuk membuat koneksi baru pada thread tambahan:
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();
}
}
});
Rekomendasi
Perlu dicatat bahwa jika pengguna mengirim semua perintah ke koneksi utama, dapat muncul situasi di mana perintah dari thread yang berbeda tercampur. Pengguna harus memahami perintah mana yang bergantung pada urutannya, dan mengambil langkah-langkah sinkronisasi untuk perintah tersebut. Juga perlu mempertimbangkan kemungkinan mengeksekusi perintah dalam sesi yang berbeda (IMAP/POP3). Operasi yang paling memakan waktu, seperti FetchMessage, AppendMessage, dan Send, mungkin lebih baik dijalankan dengan thread baru dan koneksi baru. Namun operasi cepat seperti Delete lebih tepat dijalankan dengan koneksi utama. Harap dicatat bahwa inisialisasi koneksi baru merupakan operasi yang cukup memakan waktu.