jstedfast/MailKit · error · InvalidOperationException
The ImapClient is currently busy processing a command in…
Error message
The ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads.
What it means
PopNextCommand() throws this when the engine tries to dequeue the next command while ImapClient is already busy processing a command on another thread. MailKit requires that multi-threaded users synchronize access with the ImapClient.SyncRoot lock; this error is the library telling you that contract was violated.
Solutions
- Lock imapClient.SyncRoot around every command-issuing call (Sync APIs): lock (imapClient.SyncRoot) { client.Inbox.Fetch(...) }
- Use the async APIs instead of calling Sync APIs from worker threads, so a single engine task processes commands sequentially
- Give each concurrent worker its own ImapClient/IImapClient connection
- Refactor to a single consumer thread that serializes all IMAP operations
Example fix
// before
var task1 = Task.Run(() => client.Inbox.Fetch(0, 10, summary));
var task2 = Task.Run(() => client.Inbox.Search(query));
// after
lock (client.SyncRoot) {
var msgs = client.Inbox.Fetch(0, 10, summary);
var uids = client.Inbox.Search(query);
} Defensive patterns
Strategy: validation
Validate before calling
bool SafeToRun(ImapClient client) {
lock (client.SyncRoot) { return !client.IsBusy && client.IsConnected; }
} Type guard
null
Try / catch
try {
// synchronized IMAP work
} catch (InvalidOperationException ex) when (ex.Message.Contains("currently busy")) {
logger.Warn("ImapClient contention detected - serializing access");
lock (client.SyncRoot) { /* retry once under lock */ }
} Prevention
- Centralize all IMAP access behind one synchronized service class
- Never share one ImapClient across parallel Tasks
- Convert legacy Sync calls to Async equivalents
- Document SyncRoot locking as a project convention
When it happens
Trigger: Two threads calling any ImapClient/ImapMailbox API (e.g. one calling Fetch, another calling Search or Noop) on the same connected client without holding SyncRoot; queueing a command while the engine loop is mid-command.
Common situations: A UI timer doing NOOP keepalive while a background task fetches messages; parallel Task.WhenAll over multiple mailbox operations on one client; code that queues commands but forgets the lock that existed in older versions.
Related errors
- The IMAP command queue is empty.
- The IMAP server did not return the requested message.
- The IMAP server does not support the THREAD extension.
- Value cannot be null. (Parameter 'name')
- Value cannot be null. (Parameter 'rights')
AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15).
Data as JSON: /api/errors/8933093999adf93d.
Report an issue: GitHub.
Appendix: source
Thrown at MailKit/Net/Imap/ImapEngine.cs:3136
} else if (atom.Equals ("VANISHED", StringComparison.OrdinalIgnoreCase) && folder != null) {
await folder.OnVanishedAsync (this, cancellationToken).ConfigureAwait (false);
await SkipLineAsync (cancellationToken).ConfigureAwait (false);
} else {
// don't know how to handle this... eat it?
await SkipLineAsync (cancellationToken).ConfigureAwait (false);
}
}
}
[MemberNotNull (nameof (current))]
void PopNextCommand ()
{
lock (queue) {
if (queue.Count == 0)
throw new InvalidOperationException ("The IMAP command queue is empty.");
if (IsBusy)
throw new InvalidOperationException ("The ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads.");
current = queue[0];
queue.RemoveAt (0);
try {
current.CancellationToken.ThrowIfCancellationRequested ();
} catch {
queue.RemoveAll (x => x.CancellationToken.IsCancellationRequested);
current = null;
throw;
}
}
}
/// <summary>
/// Handles an IMAP protocol exception by disconnecting and then potentially throwing a replacement exception.
/// </summary>
/// <param name="ic">The current <see cref="ImapCommand"/> being processed.</param>View on GitHub (pinned to 9d3859a785)