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