การสนับสนุนหลายเธรดในไคลเอนต์อีเมล
ไคลเอนต์เมลเช่น ImapClient และ Pop3Client อนุญาตให้ผู้ใช้ใช้ในสภาพแวดล้อมแบบหลายเธรด ไคลเอนต์เมลอนุญาตให้มีการเชื่อมต่อหนึ่งหรือหลายการเชื่อมต่อกับเซิร์ฟเวอร์ เพื่อจัดการชุดการเชื่อมต่อภายในไคลเอนต์จะใช้ connection pool จำนวนการเชื่อมต่อที่สามารถสร้างและใช้พร้อมกันได้จำกัดโดยคุณสมบัติ CredentialsByHostClient.MaxConnectionsPerServer คุณสมบัตินี้อาจตั้งค่าเป็น 1 หรือค่าที่มากกว่า โดยค่าเริ่มต้นคือ 10 คิวคำสั่งถูกนำไปใช้สำหรับการเชื่อมต่อเพื่อสนับสนุนการดำเนินการหลายเธรด คำสั่งจะดำเนินการที่ง่ายที่สุดตามโปรโตคอล เช่น Noop, Authenticate เป็นต้น ผู้ใช้อาจเริ่มการดำเนินการจำนวนคำสั่งมากกว่าจำนวนการเชื่อมต่อที่มีอยู่ แต่จะดำเนินการได้เฉพาะเมื่อไคลเอนต์สามารถสร้างการเชื่อมต่อสำหรับการดำเนินการนั้น
วิธีการใช้ไคลเอนต์เมลในสภาพแวดล้อมแบบหลายเธรด
ไคลเอนต์อีเมลมีพฤติกรรมต่อไปนี้:
1. ในกรณีที่ MaxConnectionsPerServer = 1 ไคลเอนต์จะสร้างการเชื่อมต่อ 1 ตัว ทำการยืนยันตัวตนและการให้สิทธิ์ การเชื่อมต่อนี้จะอยู่ในสถานะทำงานจนกว่าไคลเอนต์จะถูกทำลาย การดำเนินการทั้งหมดจากเธรดต่าง ๆ จะถูกส่งไปยังคิวคำสั่งเดียวที่อยู่ในการเชื่อมต่อหลัก
2. ในกรณีที่ MaxConnectionsPerServer > 1 ไคลเอนต์จะสร้างจำนวนการเชื่อมต่อที่ต้องการ ทำการยืนยันตัวตนและการให้สิทธิ์สำหรับแต่ละการเชื่อมต่อ การเชื่อมต่อหนึ่งจะถูกสงวนเป็นการเชื่อมต่อหลัก การเชื่อมต่อนี้จะคงอยู่ในสถานะทำงานจนกว่าไคลเอนต์จะถูกทำลาย การเชื่อมต่ออื่น ๆ จะถูกสร้างและทำลายตามความต้องการ จำนวนสูงสุดของการเชื่อมต่อเช่นนี้กำหนดโดยคุณสมบัติ MaxConnectionsPerServer กล่าวคือ หาก MaxConnectionsPerServer = 2 จะมีหนึ่งการเชื่อมต่อสงวนเป็นหลักและการเชื่อมต่อที่สองใช้เป็นเพิ่มเติมสำหรับการดำเนินการที่ทำในเธรดอื่น ๆ ตามลำดับ หาก MaxConnectionsPerServer = 3 จะมีการเชื่อมต่อแรกสงวนเป็นหลักและสองการเชื่อมต่ออื่นใช้เป็นเพิ่มเติมสำหรับการดำเนินการในเธรดอื่น หากมีคำขอการเชื่อมต่อจากเธรดใหม่และการเชื่อมต่อทั้งหมดถูกใช้ ไคลเอนต์จะรอจนจำนวนการเชื่อมต่อที่ใช้ลดลง นี่เป็นจุดสำคัญที่ทำให้เข้าใจว่าการทำลายการเชื่อมต่ออย่างถูกต้องมีความสำคัญเพียงใด
ตัวอย่าง
ผู้ใช้อาจดำเนินการในเธรดต่าง ๆ ได้หลายวิธี ซึ่งสามารถแบ่งออกเป็นสองประเภท:
1. ผู้ใช้ใช้เมธอดแบบอะซิงโครนัส (Begin/End) ที่กำหนดไว้ในไคลเอนต์ ในกรณีนี้ไคลเอนต์อีเมลจะเปิดเธรดใหม่ตามความต้องการ ในไคลเอ็นต์มีคิวงาน (กรุณาอย่าสับสนกับคิวคำสั่งในการเชื่อมต่อ) งานสามารถดำเนินการได้หากการเชื่อมต่อพร้อมใช้งาน เมื่อจำนวนการเชื่อมต่อที่ใช้ลดลงต่ำกว่าค่าขีดจำกัด ไคลเอ็นต์จะสร้างการเชื่อมต่อใหม่ สร้างเธรดสำหรับงานปัจจุบันและดำเนินการงานนั้น ตัวอย่างการใช้การดำเนินการแบบอะซิงโครนัส:
// 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. ผู้ใช้อาจสร้างเธรดโดยใช้วัตถุต่าง ๆ เช่น Thread, ExecutorService หรือวัตถุอื่น ๆ ที่ตั้งใจสำหรับวัตถุประสงค์นี้ นอกจากนี้ผู้ใช้อาจใช้เธรดที่สร้างโดยโค้ดของบุคคลที่สาม ระหว่างนี้ไคลเอ็นต์มีสองรูปแบบการทำงาน
a. หากผู้ใช้ไม่ได้ดำเนินการสร้างการเชื่อมต่อเพิ่มเติมสำหรับการดำเนินการในเธรดนี้ การดำเนินการทั้งหมดสำหรับเธรดนี้จะถูกส่งไปยังคิวคำสั่งของการเชื่อมต่อหลัก ตัวอย่างการดำเนินการในเธรดเพิ่มเติมโดยไม่สร้างการเชื่อมต่อใหม่ ทุกธุรกรรมทำผ่านการเชื่อมต่อหลัก:
/**
* 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. เมื่อผู้ใช้เรียกใช้เมธอดเพื่อสร้างการเชื่อมต่อใหม่สำหรับเธรดเพิ่มเติม เธรดนั้นจะถูกบล็อกจนกว่าค่าปริมาณโควต้าสำหรับการเชื่อมต่อใหม่จะถูกเปลี่ยนเพื่ออนุญาตการเชื่อมต่อใหม่ จากนั้นการเชื่อมต่อใหม่จะถูกสร้าง การเชื่อมต่อนี้จะถูกตั้งเป็นการเชื่อมต่อเริ่มต้นสำหรับทุกการดำเนินการในเธรดนี้ หลังจากที่การดำเนินการทั้งหมดในเธรดนี้เสร็จสิ้น การเชื่อมต่อต้องถูกทำลาย เพื่อสร้างการเชื่อมต่อใหม่ใช้เมธอด CredentialsByHostClient.CreateConnection เมธอดนี้จะคืนค่าอ็อบเจกต์ที่ 구현 IDisposable interface เพื่อปล่อยการเชื่อมต่อต้องเรียก Dispose การสร้างและการทำลายการเชื่อมต่อต้องดำเนินการภายในเธรดที่ทำการดำเนินการเมล หากพยายามสร้างการเชื่อมต่อใหม่ในเธรดที่สร้างไคลเอนต์เมล จะเกิดข้อผิดพลาด เนื่องจากเธรดนั้นในขณะนั้นไม่สามารถใช้สร้างการเชื่อมต่อใหม่ได้ นอกจากนี้การสร้างการเชื่อมต่อใหม่ไม่เป็นไปได้เมื่อ MaxConnectionsPerServer = 1 ตัวอย่างโค้ดการสร้างการเชื่อมต่อใหม่ในเธรดเพิ่มเติม:
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();
}
}
});
คำแนะนำ
ควรสังเกตว่าหากผู้ใช้ส่งคำสั่งทั้งหมดไปยังการเชื่อมต่อหลัก อาจเกิดสถานการณ์ที่คำสั่งจากเธรดต่าง ๆ ปะปนกัน ผู้ใช้ควรเข้าใจว่าคำสั่งใดพึ่งพาลำดับของมันและต้องดำเนินการเพื่อซิงโครไนซ์คำสั่งเหล่านั้น นอกจากนี้ยังต้องพิจารณาความเป็นไปได้ของการดำเนินคำสั่งในเซสชันต่าง ๆ (IMAP/POP3) งานที่ใช้เวลามากที่สุด เช่น FetchMessage, AppendMessage และ Send อาจเหมาะกับการทำในเธรดใหม่และการเชื่อมต่อใหม่ แต่งานที่เร็วเช่น Delete ควรทำกับการเชื่อมต่อหลัก โปรดทราบว่าการเริ่มต้นการเชื่อมต่อใหม่เป็นงานที่กินเวลามากพอสมควร