Client sync queue: better logic around setTimeout

In the old code, if you queued a task, did something which flushes the
queue (runTask, flush, or drain), and then queued another task, you'd
end up with two pending timeouts, rather than zero. With this change,
we're careful to never have two pending timeouts, and also to clear the
timeout if we're about to run all current tasks.