C # mailkit imap режим ожидания клиента не отменяется - PullRequest
0 голосов
/ 13 июня 2019

при получении новой электронной почты запускается метод остановки бездействия, и там говорится, что клиент 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);

декларации, относящиеся к холостому ходу

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

1 Ответ

0 голосов
/ 14 июня 2019

Проблема в том, что вы ожидаете присоединения потока из обратного вызова события ImapClient, что означает, что вы блокируете ImapClient от продолжения.

Решение: не делай этого.

События IMAP MailKit отправляются, когда процессор команд IMAP все еще обрабатывает ответы сервера, поэтому вы не можете вызывать больше команд для того же ImapClient в этих обработчиках событий.

Вместо этого вам нужно реализовать какую-либо очередь команд в вашей программе, а затем, в обработчике событий CountChanged (или любом другом обработчике, который вы обрабатываете), поставить в очередь следующую команду (и), чтобы вызвать ее сразу после текущей команды. завершается.

Самый простой способ сделать это - сохранить System.Threading.Tasks.Task где-нибудь, где ваш обработчик событий имеет к нему доступ и затем может сделать:

task = task.ContinueWith (...);

Это простой способ реализации очереди команд.

...