邮件客户端中的多线程支持

像 ImapClient 和 Pop3Client 这样的邮件客户端允许用户在多线程环境中使用它们。邮件客户端可以与服务器建立一个或多个连接。为了在客户端内部管理一组连接,使用了连接池。可同时创建和使用的连接数量受属性 CredentialsByHostClient.MaxConnectionsPerServer 限制。该属性可以设置为 1 或更大的值,默认值为 10。为支持多线程操作,已为连接实现了命令队列。命令实现了协议中定义的最简单操作,如 Noop、Authenticate 等。用户可以启动的命令数量可能超过可用连接数量。但这些命令只有在客户端能够为其创建连接时才会执行。

在多线程环境中使用邮件客户端的方法

邮件客户端具有以下行为:

1. 当 MaxConnectionsPerServer = 1 时,客户端仅创建 1 个连接并执行身份验证和授权。该连接在客户端被释放之前保持工作状态。来自不同线程的所有操作都会被指向放在主连接中的单一命令队列。

2. 当 MaxConnectionsPerServer > 1 时,客户端会创建所需数量的连接,并对每个连接执行身份验证和授权。保留一个连接作为主连接。该连接在客户端被释放之前一直保持工作状态。其他连接根据需求创建和释放。此类连接的最大数量由属性 MaxConnectionsPerServer 定义,例如,如果 MaxConnectionsPerServer = 2,则保留一个作为主连接,第二个连接作为额外连接用于在其他线程中执行的操作。同理,如果 MaxConnectionsPerServer = 3,则第一个连接为主连接,另外两个连接作为额外连接用于在其他线程中执行的操作。如果来自新线程的下一次连接请求到来,而所有连接均已被占用,客户端将等待,直到已使用的连接数量减少。此关键点阐明了正确释放连接为何如此重要。

示例

用户可以通过多种方式在不同线程中执行操作。它们可以分为两类:

1. 用户使用客户端定义的异步(Begin/End)方法。在这种情况下,邮件客户端在需要时会启动新线程。客户端实现了任务队列(请勿与连接中的命令队列混淆)。如果有可用连接,任务即可执行。当已使用的连接数量少于限制值时,客户端会创建新连接,为当前任务创建线程并执行该任务。以下是使用异步操作的示例:

2. 用户可以使用 Thread、ThreadPool、Task 或其他任何用于此目的的对象来创建线程。也可以使用第三方代码创建的线程。在此期间,客户端有两种行为模型

a. 如果用户没有为该线程的操作创建额外的连接,所有该线程的操作将被发送到主连接的命令队列。以下是未创建新连接的额外线程中操作的示例,所有事务均通过主连接进行:

b. 当用户调用方法为额外线程创建新连接时,该线程会被阻塞,直到新连接的配额值被更改以允许创建新连接。随后创建新连接,并将其设为该线程中所有操作的默认连接。该线程中的所有操作完成后,必须释放该连接。要创建新连接,请使用 CredentialsByHostClient.CreateConnection 方法。此方法返回实现 IDisposable 接口的对象。要释放连接,必须调用 Dispose 方法。连接的创建和释放必须在执行邮件操作的线程内部进行。在创建邮件客户端的线程中尝试创建新连接会导致错误,因为此时该线程不能用于创建新连接。当 MaxConnectionsPerServer = 1 时也无法创建新连接。以下是创建新连接的额外线程的代码示例:

建议

值得注意的是,如果用户将所有命令发送到主连接,可能会出现来自不同线程的命令混在一起的情况。用户应了解哪些命令依赖于其执行顺序,并采取措施同步这些命令。还需考虑在不同会话(IMAP/POP3)中执行命令的可能性。最耗时的操作,如 FetchMessage、AppendMessage 和 Send,可能需要在新线程和新连接中执行。但像 Delete 这样快速的操作则应使用主连接。请注意,初始化新连接本身已经是一个耗时操作。