E-posta istemcilerinde çoklu iş parçacığı desteği

Şu gibi mail istemcileri ImapClient ve Pop3Client Kullanıcıların çoklu iş parçacığı ortamında kullanabilmesini sağlar. Mail istemcileri, bir sunucu ile bir veya daha fazla bağlantı kurmaya izin verir. İstemciler içinde bir bağlantı havuzu kullanılarak bağlantı seti yönetilir. Aynı anda oluşturulup kullanılabilecek bağlantı sayısı CredentialsByHostClient.MaxConnectionsPerServer özelliğiyle sınırlanır. Bu özellik 1 veya daha büyük bir değere ayarlanabilir. Varsayılan olarak bu değer 10’dur. Bağlantı için çoklu iş parçacığı işlemlerini desteklemek amacıyla bir komut kuyruğu uygulanmıştır. Komutlar, protokolde tanımlı en basit işlemleri (Noop, Authenticate vb.) uygular. Kullanıcı, mevcut bağlantı sayısından daha fazla komut çalıştırmaya başlayabilir. Ancak bu komutlar, istemci işlemi gerçekleştirecek bir bağlantı oluşturabildiğinde yürütülür.

Çoklu İş Parçacığı Ortamında Mail İstemcilerini Kullanma Yöntemleri

E-posta istemcileri aşağıdaki davranışa sahiptir:

1. MaxConnectionsPerServer = 1 olduğunda istemci 1 bağlantı oluşturur, kimlik doğrulama ve yetkilendirme yapar. Bu bağlantı, istemci vazgeçirilene kadar çalışır durumda tutulur. Farklı iş parçacıklarından gelen tüm işlemler, ana bağlantıda bulunan tek bir komut kuyruğuna yönlendirilir.

2. MaxConnectionsPerServer > 1 olduğunda istemci gerekli sayıda bağlantı oluşturur, her bağlantı için kimlik doğrulama ve yetkilendirme yapar. Bir bağlantı ana bağlantı olarak ayrılır. Bu bağlantı, istemci vazgeçirilene kadar çalışır durumda tutulur. Diğer tüm bağlantılar talep üzerine oluşturulur ve vazgeçirilir. Bu tür bağlantıların azami sayısı MaxConnectionsPerServer özelliğiyle tanımlanır; örneğin MaxConnectionsPerServer = 2 ise biri ana bağlantı olarak ayrılır, ikinci bağlantı diğer iş parçacıklarında yürütülen işlemler için ek bağlantı olarak kullanılır. Benzer şekilde MaxConnectionsPerServer = 3 ise ilk bağlantı ana bağlantı, iki diğer bağlantı ise ek olarak kullanılır. Yeni bir iş parçacığından bağlantı talebi geldiğinde ve tüm bağlantılar zaten kullanımda olduğunda, istemci kullanılabilir bağlantı sayısı azalıncaya kadar bekler. Bu, bağlantıların doğru şekilde vazgeçirilmesinin neden bu kadar önemli olduğunu açıklayan çok önemli bir noktadır.

Örnekler

Kullanıcı, farklı iş parçacıklarında çeşitli şekillerde işlemler yürütebilir. Bunlar iki tipe ayrılabilir:

1. Kullanıcı, istemcide tanımlı asenkron (Begin/End) metodları kullanır. Bu durumda e-posta istemcisi ihtiyaç duyulduğunda yeni iş parçacıkları başlatır. İstemcide görev kuyruğu uygulanmıştır (lütfen bağlantıdaki komut kuyruğu ile karıştırmayın). Görev, bağlantı mevcutsa yürütülebilir. Kullanılan bağlantı sayısı limit değerinden düşük olduğunda, istemci yeni bir bağlantı oluşturur, mevcut görev için bir iş parçacığı yaratır ve bu görevi yürütür. Asenkron operasyonların kullanımı örneği:

// 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. Kullanıcı, bu amaçla Thread, ExecutorService veya başka nesneler gibi nesneler kullanarak iş parçacıkları oluşturabilir. Ayrıca kullanıcı, üçüncü taraf kod tarafından oluşturulan iş parçacıklarını da kullanabilir. Bu esnada istemcinin iki davranış modeli vardır

a. Kullanıcı, iş parçacığındaki işlemler için ek bağlantı oluşturmadıysa, bu iş parçacığının tüm işlemleri ana bağlantıya gönderilecek komut kuyruğuna yönlendirilir. Yeni bağlantı oluşturmadan ek bir iş parçacığında yapılan işlemlere bir örnek; tüm işlemler ana bağlantı üzerinden gerçekleştirilir:

/**
 * 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. Kullanıcı, ek bir iş parçacığı için yeni bir bağlantı oluşturma metodunu çalıştırdığında, bu iş parçacığı yeni bağlantılara izin verilene kadar kota değeri değişene kadar bloklanır. Ardından yeni bağlantı oluşturulur. Bu bağlantı, bu iş parçacığındaki tüm işlemler için varsayılan bağlantı olarak ayarlanır. İş parçacığındaki tüm işlemler tamamlandıktan sonra bağlantı vazgeçirilmelidir. Yeni bağlantılar oluşturmak için CredentialsByHostClient.CreateConnection metodunu kullanın. Bu yöntem, IDisposable arayüzünü uygulayan bir nesne döndürür. Bağlantıyı serbest bırakmak için Dispose metodu çağrılmalıdır. Bağlantının oluşturulması ve vazgeçirilmesi, e-posta işlemlerinin yürütüldüğü iş parçacığı içinde gerçekleştirilmelidir. Mail istemcisinin oluşturulduğu iş parçacığında yeni bir bağlantı oluşturma girişimi bir hataya yol açar, çünkü bu iş parçacığı o anda yeni bağlantı oluşturmak için kullanılamaz. Ayrıca MaxConnectionsPerServer = 1 iken yeni bağlantı oluşturulması mümkün değildir. Ek bir iş parçacığı için yeni bağlantı oluşturma kod örneği:

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();
        }
    }
});

Öneriler

Kullanıcının tüm komutları ana bağlantıya gönderdiği durumda, farklı iş parçacıklarından gelen komutların karışması gibi bir durum ortaya çıkabilir. Kullanıcı, hangi komutların sıralarına bağlı olduğunu anlamalı ve bu komutların senkronizasyonu için önlemler almalıdır. Ayrıca komutların farklı oturumlarda (IMAP/POP3) çalıştırılması olasılığı da göz önünde bulundurulmalıdır. FetchMessage, AppendMessage ve Send gibi en zaman alıcı işlemler, muhtemelen yeni bir iş parçacığı ve yeni bir bağlantı ile gerçekleştirilmelidir. Ancak Delete gibi hızlı işlemler ana bağlantı ile yapılabilir. Lütfen yeni bağlantının başlatılmasının zaten zaman tüketen bir işlem olduğunu unutmayın.