如果您使用某些 MailKit 方法并对 API 进行一些并发访问,您将收到如下所示的
InvalidOperationException
异常:
ImapClient 当前正忙于处理另一个线程中的命令。锁定 SyncRoot 属性以正确同步您的线程。
您通常会这样做,正如错误消息所建议的那样:
lock (imapClient.SyncRoot)
{
imapClient.GetFolder(/* ... */)
}
但是,如果您确实想保留所有可能以异步方式使用的内容,您可以这样做:
lock (imapClient.SyncRoot)
{
await imapClient.GetFolderAsync(/* ... */)
}
表达式不能在lock语句的范围内使用。await
我已经阅读并发现了许多与此主题相关的问题,但我仍然不清楚最好的解决方案。如果这从根本上不兼容,那么应该如何使用异步 Mailkit API?如果这从根本上与该方法不兼容,为什么它会建议
.SyncRoot
?
这里的最佳实践是什么?
我发现的唯一易于实施的解决方案是使用
SemaphoreSlim
就像这里建议的那样。这也是也是问题的通用解决方案。
它可以例如通过继承来实现
ImapClient
:
using MailKit.Net.Imap;
/// <remarks>
/// Using multiple instances of <see cref="MailKit.Net.Imap.ImapClient"/> to connect to the same mailbox at the same time is very likely to cause issues.
/// This is because an IMAP server simply refuses to take new commands when it is currently busy executing another command. To make a single ImapClient instance
/// accessible throughout the app, to be used by multiple concurrent Tasks, we can restrict access to it by using a semaphore that can be locked and released
/// before and after using the ImapClient.
/// </remarks>
/// <inheritdoc cref="ImapClient"/>
public interface IImapClientWithSemaphore : IImapClient
{
/// <summary>
/// <b>Always</b> wait for the semaphore before executing any relevant command. See <see cref="IImapClientWithSemaphore"/> for a detailed explanation on why.
/// </summary>
public SemaphoreSlim Semaphore { get; }
}
实现如下:
/// <inheritdoc cref="IImapClientWithSemaphore"/> />
public class ImapClientWithSemaphore : ImapClient, IImapClientWithSemaphore
{
/// <inheritdoc cref="IImapClientWithSemaphore"/> />
public SemaphoreSlim Semaphore { get; } = new(1, 1);
/// <inheritdoc cref="ImapClient"/> />
public ImapClientWithSemaphore() { }
/// <inheritdoc cref="ImapClient"/> />
public ImapClientWithSemaphore(IProtocolLogger protocolLogger) : base(protocolLogger) { }
}
然后你就可以这样做:
await imapClient.Semaphore.WaitAsync(CancellationToken.None);
try
{
await imapClient.GetFolderAsync(/* ... */)
}
finally
{
imapClient.Semaphore.Release();
}