简体   繁体   English

C#mailkit imap客户端空闲模式未被取消

[英]C# mailkit imap client idle mode not getting cancelled

when new email is received, the idle stop method is ran and there it says Imap client is currently busy processing in another thread. 收到新电子邮件时,会运行idle stop方法,并说Imap客户端当前正忙着处理另一个线程。 i assume its because idle command is still running in the background thread ? 我假设它因为空闲命令仍然在后台线程中运行? even tho i called thread.Join() method, it wont end. 甚至我称之为thread.Join()方法,它不会结束。 i am sturck here for quite some time, and the example demo on MailKit github only shows how to handle it with help from manual user input such as Console.ReadKey(). 我在这里徘徊了一段时间,而MailKit github上的示例演示只显示了如何在手动用户输入(如Console.ReadKey())的帮助下处理它。 i am pretty sure i am missing some major point or the code have major flaws but i have searched many times for an answer and there doesnt seem to be any major results other than the github example 我很确定我错过了一些重点或代码有重大缺陷但我已多次搜索答案,除了github示例之外似乎没有任何重大结果

protocol logger when idle start and message recevied until the exception occurs 空闲启动时收到协议记录器,收到消息直到异常发生

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

method which starts idle 开始闲置的方法

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

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

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

declarations related to 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);
    }

stop idle method 停止闲置的方法

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

idle loop method 空闲循环方法

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

The problem is that you are waiting for the thread to join from within the ImapClient's event callback which means you are blocking the ImapClient from continuing. 问题是您正在等待线程从ImapClient的事件回调中加入,这意味着您阻止ImapClient继续。

The solution is: don't do that. 解决方案是:不要那样做。

MailKit's IMAP events are emitted while the IMAP command processor is still processing the server responses, so you cannot invoke more commands on the same ImapClient within those event handlers. 当IMAP命令处理器仍处理服务器响应时,会发出MailKit的IMAP事件,因此您无法在这些事件处理程序中的同一ImapClient上调用更多命令。

What you need to do instead is to implement some sort of command queue in your program and then, within the CountChanged event handler (or whatever handler you are handling), queue the next command(s) to invoke once the current command completes. 您需要做的是在程序中实现某种命令队列,然后在CountChanged事件处理程序(或您正在处理的任何处理程序)中,在当前命令完成后对下一个命令进行排队以进行调用。

An easy way to do this is to keep a System.Threading.Tasks.Task somewhere where your event handler has access to it and can then do: 一个简单的方法是将System.Threading.Tasks.Task保存在事件处理程序可以访问它的位置,然后执行:

task = task.ContinueWith (...);

That's a simple way of implementing a command queue. 这是实现命令队列的简单方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM