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

  1. Lock imapClient.SyncRoot around every command-issuing call (Sync APIs): lock (imapClient.SyncRoot) { client.Inbox.Fetch(...) }
  2. Use the async APIs instead of calling Sync APIs from worker threads, so a single engine task processes commands sequentially
  3. Give each concurrent worker its own ImapClient/IImapClient connection
  4. 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

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


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)