【问题标题】:C# mailkit imap client idle mode not getting cancelledC# mailkit imap 客户端空闲模式没有被取消
【发布时间】:2019-06-14 10:34:51
【问题描述】:

当收到新邮件时,idle stop 方法运行,它说 Imap 客户端当前正忙于在另一个线程中处理。我假设它是因为空闲命令仍在后台线程中运行? 即使我调用了 thread.Join() 方法,它也不会结束。我在这里呆了很长一段时间,MailKit github 上的示例演示仅显示了如何在手动用户输入(例如 Console.ReadKey() )的帮助下处理它。 我很确定我遗漏了一些要点或代码有重大缺陷,但我已经多次搜索答案,除了 github 示例之外似乎没有任何主要结果

闲置开始时的协议记录器并在异常发生之前收到消息

S: * OK [UIDNEXT 21641] Predicted next UID.
S: * OK [HIGHESTMODSEQ 881089]
S: A00000006 OK [READ-WRITE] INBOX selected. (Success)
C: A00000007 IDLE
S: + idling
S: * 21512 EXISTS
C: DONE

启动空闲的方法

        IdleClient.Inbox.MessageExpunged += OnMessageExpunged;
        IdleClient.Inbox.CountChanged += OnInboxCountChanged;

        ImapToken = new CancellationTokenSource();
        SetTokenValues(ImapToken.Token);

        ImapToken.Token.ThrowIfCancellationRequested();
        ImapThreadInfo = Helpers.InBackgroundThread(ImapIdleLoop, UniqueAccountId, true);

idle相关的声明

    private (int, Thread) ImapThreadInfo;
    private CancellationToken CancellationToken { get; set; }
    private CancellationToken DoneToken { get; set; }
    private CancellationTokenSource ImapToken { get; set; }
    private CancellationTokenSource Timeout { get; set; }


    private bool IsCancellationRequested => CancellationToken.IsCancellationRequested || DoneToken.IsCancellationRequested;
    private readonly object Mutex = new object();

    private void CancelTimeout() {
        lock (Mutex) {
            Timeout?.Cancel();
        }
    }

    private void SetTimeoutSource(CancellationTokenSource source) {
        lock (Mutex) {
            Timeout = source;

            if (Timeout != null && IsCancellationRequested) {
                Timeout.Cancel();
            }
        }
    }

    private void SetTokenValues(CancellationToken doneToken, CancellationToken cancellationToken = default) {
        CancellationToken = cancellationToken;
        DoneToken = doneToken;
        doneToken.Register(CancelTimeout);
    }

停止空闲方法

public void StopImapIdle(bool clientDisconnect) {
        ImapToken.Cancel();
        try {
            Task.Factory.StartNew(() => {
                ImapThreadInfo.Item2?.Join();
            });
            ImapToken.Dispose();

            if (!clientDisconnect) {
                return;
            }

            if (IdleClient.IsConnected && IdleClient.IsIdle) {
                while (true) {
                    if (!IdleClient.IsIdle) {
                        BotLogger.Log("Idling has been stopped.", LogLevels.Trace);
                        break;
                    }
                    BotLogger.Log("Waiting for idle client to stop idling...", LogLevels.Trace);
                }
            }

            lock (IdleClient.SyncRoot) {
                //Error here
                IdleClient.Disconnect(true);
                BotLogger.Log("Imap client has been disconnected.", LogLevels.Trace);
            }
        }
        catch (NullReferenceException) {
            BotLogger.Log("There is no thread with the specified uniqueID", LogLevels.Warn);
        }
        IsAccountLoaded = false;
    }

空闲循环方法

private void ImapIdleLoop() {
        while (!IsCancellationRequested) {
            Timeout = new CancellationTokenSource(new TimeSpan(0, 9, 0));

            try {
                SetTimeoutSource(Timeout);
                if (IdleClient.Capabilities.HasFlag(ImapCapabilities.Idle)) {
                    lock (IdleClient.SyncRoot) {
                        IdleClient.Idle(Timeout.Token, CancellationToken);
                    }
                }
                else {
                    lock (IdleClient.SyncRoot) {
                        IdleClient.NoOp(CancellationToken);
                    }
                    WaitHandle.WaitAny(new[] { Timeout.Token.WaitHandle, CancellationToken.WaitHandle });
                }
            }
            catch (OperationCanceledException) {
                // This means that idle.CancellationToken was cancelled, not the DoneToken nor the timeout.
                break;
            }
            catch (ImapProtocolException) {
                // The IMAP server sent garbage in a response and the ImapClient was unable to deal with it.
                // This should never happen in practice, but it's probably still a good idea to handle it.
                // 
                // Note: an ImapProtocolException almost always results in the ImapClient getting disconnected.
                IsAccountLoaded = false;
                break;
            }
            catch (ImapCommandException) {
                // The IMAP server responded with "NO" or "BAD" to either the IDLE command or the NOOP command.
                // This should never happen... but again, we're catching it for the sake of completeness.
                break;
            }
            catch (SocketException) {


            }
            catch (ServiceNotConnectedException) {

            }
            catch (IOException) {

            }
            finally {
                // We're about to Dispose() the timeout source, so set it to null.
                SetTimeoutSource(null);
            }
            Timeout?.Dispose();
        }
    }

【问题讨论】:

    标签: c# imap mailkit


    【解决方案1】:

    问题是您正在等待线程从 ImapClient 的事件回调中加入,这意味着您正在阻止 ImapClient 继续。

    解决办法是:不要那样做。

    MailKit 的 IMAP 事件是在 IMAP 命令处理器仍在处理服务器响应时发出的,因此您无法在这些事件处理程序中对同一个 ImapClient 调用更多命令。

    您需要做的是在您的程序中实现某种命令队列,然后在 CountChanged 事件处理程序(或您正在处理的任何处理程序)中,将下一个命令排队以在当前命令调用一次完成。

    一个简单的方法是将System.Threading.Tasks.Task 保存在您的事件处理程序可以访问它的地方,然后可以这样做:

    task = task.ContinueWith (...);
    

    这是一种实现命令队列的简单方法。

    【讨论】:

      猜你喜欢
      • 2022-09-30
      • 2021-11-27
      • 1970-01-01
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-25
      相关资源
      最近更新 更多