when new email is received, the idle stop method is ran and there it says Imap client is currently busy processing in another thread. i assume its because idle command is still running in the background thread ? even tho i called thread.Join() method, it wont end. 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(). 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
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();
}
}