Поддръжка на многонитово програмиране в имейл клиенти
Имейл клиенти като ImapClient и Pop3Client Позволяват на потребителите да ги използват в многонитова среда. Имейл клиентите позволяват една или повече връзки със сървър. За управление на набор от връзки в клиентите се използва пул от връзки. Броят на връзките, които могат да се създадат и използват едновременно, е ограничен от свойството 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. За освобождаване на връзката трябва да се извика методът 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, имат смисъл да се изпълняват с главната връзка. Моля, имайте предвид, че инициализирането на нова връзка е достатъчно времеемка операция.