メールクライアントにおけるマルチスレッドサポートと接続プール

次のようなメールクライアント: ImapClient, Pop3Client、および SmtpClient はマルチスレッド環境で使用できます。クライアントはサーバーとの接続を 1 つまたは複数保持できます。クライアント内の接続集合を管理するために 接続プール が使用されます。同時に作成・使用できる接続数は CredentialsByHostClient.MaxConnectionsPerServer プロパティです。このプロパティは 1 以上の値に設定できます。デフォルトは 10 です。

各接続にはマルチスレッド操作をサポートするコマンドキューが実装されています。コマンドはプロトコルで定義された最も単純な操作(例: Noop, Authenticate、など。ユーザーは利用可能な接続数以上のコマンドを開始できても、クライアントが操作用に接続を作成できるようになるまで実行は待機します。

マルチスレッド環境におけるメールクライアントの動作

メールクライアントは以下のような動作をします:

  1. の場合、 MaxConnectionsPerServer = 1クライアントは 1 つの接続を作成し、認証と認可を行います。この接続はクライアントが破棄されるまで稼働状態を保ち、異なるスレッドからのすべての操作はメイン接続内の 1 つのコマンドキューに送られます。

  2. の場合、 MaxConnectionsPerServer > 1、クライアントは必要な数の接続を作成し、各接続に対して認証と認可を行います。1 つの接続がメイン接続として予約されます。この接続はクライアントが破棄されるまで稼働状態を保ちます。他の接続はオンデマンドで作成および破棄されます。接続の最大数は MaxConnectionsPerServer プロパティです。例えば、 MaxConnectionsPerServer = 2、1 つの接続がメイン接続として予約され、2 番目の接続が他のスレッドで実行される操作用の追加接続として使用されます。したがって、 MaxConnectionsPerServer = 3、最初の接続がメイン接続として予約され、残りの 2 つの接続が他のスレッドで実行される操作の追加接続として使用されます。新しいスレッドから接続要求があり、すべての接続が使用中の場合、クライアントは使用中の接続数が減少するまで待機します。これは、接続を正しく破棄することが極めて重要である理由を明らかにする重要なポイントです。

マルチスレッド環境でのメールクライアント使用例

ユーザーはさまざまな方法で異なるスレッドで操作を実行できます。大きく 2 種類に分けられます。

非同期(Begin/End)メソッドの使用

ユーザーはクライアントで定義された非同期(Begin/End)メソッドを使用します。この場合、メールクライアントは必要に応じて新しいスレッドを起動します。クライアントにはタスクキューが実装されており(接続のコマンドキューとは混同しないでください)、接続が利用可能な場合にタスクが実行されます。使用中の接続数が上限値未満になると、クライアントは新しい接続を作成し、現在のタスク用にスレッドを生成してタスクを実行します。非同期操作の使用例:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.Host = "domain.com";
client.Username = "username";
client.Password = "password";
client.SelectFolder("InBox");

ImapMessageInfoCollection messages = client.ListMessages();
IAsyncResult res1 = client.BeginFetchMessage(messages[0].UniqueId);
IAsyncResult res2 = client.BeginFetchMessage(messages[1].UniqueId);
MailMessage msg1 = client.EndFetchMessage(res1);
MailMessage msg2 = client.EndFetchMessage(res2);

ユーザー作成スレッドの使用

ユーザーは次のようなオブジェクトを使用してスレッドを作成できます Thread, ThreadPool, Task、またはこの目的のためのその他のオブジェクトです。ユーザーはサードパーティコードで作成されたスレッドも使用できます。この場合、クライアントには 2 つの動作モデルがあります。

a. ユーザーがスレッド内の追加接続作成を行わなかった場合、すべての操作はメイン接続のコマンドキューに送られます。以下は新しい接続を作成せずに追加スレッドで実行する操作の例です — すべてのトランザクションはメイン接続を介して行われます:

List<MailMessage> List = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    client.SelectFolder("folderName");
    ImapMessageInfoCollection messageInfoCol = client.ListMessages();
    foreach (ImapMessageInfo messageInfo in messageInfoCol)
    {
        List.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

b. ユーザーが追加スレッド用に新しい接続を作成するメソッドを実行すると、そのスレッドは新しい接続のクォータが変更されて許可されるまでブロックされます。その後新しい接続が作成され、当該スレッドのすべての操作のデフォルト接続として設定されます。スレッドのすべての操作が完了したら、接続は破棄する必要があります。新しい接続を作成するには、 CredentialsByHostClient.CreateConnection メソッドです。このメソッドは IDisposable インターフェイスです。接続を解放するには、 Dispose メソッドを呼び出す必要があります。接続の作成と破棄は、メール操作が実行されるスレッド内で行う必要があります。メールクライアントが作成されたスレッドで新しい接続を作成しようとするとエラーになります。この時点でそのスレッドは新しい接続を作成できないためです。また、 MaxConnectionsPerServer = 1. 追加スレッドで新しい接続を作成するコード例:

List<MailMessage> List1 = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    using (IDisposable connection = client.CreateConnection())
    {
        client.SelectFolder("FolderName");
        ImapMessageInfoCollection messageInfoCol = client.ListMessages();
        foreach (ImapMessageInfo messageInfo in messageInfoCol)
            List1.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

接続プール

Aspose.Email 19.3 以降、接続プールがリファクタリングされました。The EmailClient クラスが導入され、最終的に CredentialsByHostClient クラス。 EmailClient クラスは以下を提供します。 ConnectionAsgmtMode マルチスレッド環境での接続割り当てモードを定義するプロパティです。 EmailClient.ConnectionAsgmtMode は以下を使用して設定されます。 ConnectionAsgmtType 列挙体です。

接続タイプ

接続タイプは 3 種類あります:

  • メイン接続。 これはメールクライアントと同時に作成・破棄される接続で、手動で作成または破棄することはできません。
  • デフォルト接続。 ユーザーはスレッド用にデフォルト接続を CreateConnection メソッドで手動作成できます。デフォルト接続が存在すれば、スレッド内で実行されるメールクライアントのすべてのメソッドは暗黙的にこの接続を使用します。スレッドごとにデフォルト接続は 1 つだけ存在できます。作成は手動または自動で行われ、 EmailClient.ConnectionAsgmtMode プロパティです。これらの接続は EmailClient.CreateConnection(createAsDefaultConnection = true) メソッドで作成できます。デフォルト接続が使用されていない場合(接続割り当てモードに依存)、メイン接続が暗黙的に代わりに使用されます。
  • 独立接続。 これはスレッドに紐付かない接続です。手動で作成し、メソッドパラメーターとして明示的に使用する必要があります。これらの接続は EmailClient.CreateConnection() メソッドまたは EmailClient.CreateConnection(createAsDefaultConnection = false) メソッド。

接続割り当てタイプ

設定するには EmailClient.ConnectionAsgmtMode プロパティ、 ConnectionAsgmtType 列挙が使用されます。以下に提供される割り当てタイプを示します。

  • ConnectionAsgmtType.UseMainOrDefault このモードはメールクライアントのデフォルトです。デフォルト接続が作成されていない、またはメソッドパラメーターとして接続が明示的に渡されていない場合、メールクライアントはメイン接続を使用します。メイン接続はメールクライアントと同時に作成されます。ユーザーは CreateConnection メソッドで使用されます。スレッド用にデフォルト接続が作成されている場合、そのスレッドで呼び出されたすべてのメールクライアントメソッドは暗黙的にその接続を使用します。デフォルト接続が作成されていない場合、メイン接続がそのスレッド内のすべてのメソッドで使用されます。ユーザーはスレッドに紐付かない接続(デフォルトでない接続)も CreateConnection メソッドです。他の接続(メインでもデフォルトでもない)を使用するには、メソッドのパラメーターとして接続を明示的に渡す必要があります。ユーザーは必要に応じて任意の数の接続を作成できます。スレッドごとに存在できるデフォルト接続は 1 つだけです。デフォルト接続は Thread マルチタスクプログラミング用のオブジェクトです。ユーザーが接続プールや Task マルチタスク用のオブジェクトで、このモードでは不正な動作につながる恐れがあります。この問題を回避するには、コード実行の最後にデフォルト接続(使用している場合)を手動で破棄してください。

  • ConnectionAsgmtType.UseMain メールクライアントは、複数スレッドからのすべての操作でメイン接続を使用します。メイン接続はメールクライアントと同時に作成されます。ユーザーはデフォルト接続を作成できませんが、スレッドに紐付かない接続は CreateConnection メソッドでスレッド用のデフォルト接続を作成できません。他の接続を使用するには、メソッドパラメーターとして明示的に渡す必要があります。

  • ConnectionAsgmtType.UseDefault メールクライアントは、複数スレッドからのすべての操作でデフォルト接続のみを暗黙的に使用します。このモードではメイン接続は使用されません。スレッドの最初のメソッド呼び出し時にデフォルト接続が作成されていない場合、メールクライアントは最初の操作が実行される前にそのスレッド用にデフォルト接続を暗黙的に作成します。ユーザーは CreateConnection メソッドで自動的に作成されます。ユーザーはスレッドに紐付かない接続も CreateConnection メソッドを使用します。他の接続を使用するには、メソッドパラメーターとして明示的に渡す必要があります。ユーザーは必要に応じて任意の数の接続を作成できます。スレッドごとに使用できるデフォルト接続は 1 つだけです。デフォルト接続は、 Thread マルチタスクプログラミング用のオブジェクトです。ユーザーが接続プールや Task マルチタスク用のオブジェクトで、このモードでは不正な動作につながる恐れがあります。この問題を回避するには、コード実行の最後にデフォルト接続を手動で破棄する必要があります。

推奨事項

ユーザーがすべてのコマンドをメイン接続に送信すると、異なるスレッドからのコマンドが混在する状況が生じる可能性があります。ユーザーはコマンドの実行順序に依存するものを把握し、同期措置を講じる必要があります。また、異なるセッション(IMAP/POP3)でコマンドを実行する可能性も考慮すべきです。最も時間のかかる操作は FetchMessage, AppendMessage、および Send. これらの操作は新しいスレッドと新しい接続で実行した方が合理的でしょう。短時間で終わる操作は Delete メイン接続で実行するのが妥当です。新しい接続の初期化はかなり時間のかかる操作であることに注意してください。