การสนับสนุนหลายเธรดในไคลเอนต์อีเมล

ไคลเอนต์เมลเช่น 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 ควรทำกับการเชื่อมต่อหลัก โปรดทราบว่าการเริ่มต้นการเชื่อมต่อใหม่เป็นงานที่กินเวลามากพอสมควร