It took way too long until I fully understood what you wrote. I think the key thing that I read, but didn't properly register in my mind was the fact that `waitFor`-ing on _anything_ will do async-work on _everything_.
That was the puzzle piece that I was missing, since I wanted to do some async work occasionally here or there, but not to the point that it might stop me from immediately responding to messages. I wrote myself a small example on how I'd set it up (basically a compileable version of the example you wrote) and will likely end up translating into my actual project. It sends a batch of messages, signals the other thread to work through that batch and then it waitFors on the signal again, the echo's proving in between the batches that async work is being done even though the waitFor is being done on the signal. import chronos import chronos/threadsync import std/[os, sequtils, atomics] import threading/channels var chan = newChan[int](50) let signal = ThreadSignalPtr.new()[] var thread = Thread[void]() var keepRunning: Atomic[bool] keepRunning.store(true) proc senderThread() {.thread.} = for i in 0..5: for y in i*3..(i+1)*3: chan.send(y) echo signal.fireSync() sleep(2000) keepRunning.store(false) echo signal.fireSync() proc process1Message(x: int) {.async.} = echo "before ", x await sleepAsync(1.seconds) echo "after ", x proc main() = thread.createThread(senderThread) var msg: int while keepRunning.load(): echo "\nNEW LOOP" while chan.tryRecv(msg): try: asyncSpawn msg.process1Message() except CatchableError as e: echo "Failed for message: ", msg waitFor signal.wait() joinThread(thread) echo "FINISHED" main() Run