Level 1 One dispatcher thread
Build DelayedScheduler, a small in-process timer service. Callers hand it a function and a delay; the scheduler calls the function later, on its own background thread, while callers carry on.
DelayedScheduler(): start the scheduler and its background thread (make it a daemon thread, so a scheduler nobody shuts down can't keep the program alive).schedule(task, delay_ms) -> int: arrange fortask()(a function with no arguments) to be called once, no earlier thandelay_msmilliseconds after this call. Return immediately with the task's ID:1, 2, 3, ...in the orderscheduleis called. A delay of0(or a negative one) means "as soon as possible".pending() -> int: how many scheduled tasks haven't started yet.shutdown(wait=True) -> None: stop accepting tasks; afterwardsscheduleraisesRuntimeError.wait=True: every pending task still runs at its due time;shutdownreturns once they have all finished and the background thread has exited.wait=False: pending tasks are dropped and never run; a task that is already running is allowed to finish; then the thread exits andshutdownreturns.- Calling
shutdownagain does nothing.
Ordering: tasks run one at a time on the single background thread, in order of their due time (time of schedule call + delay); equal due times run in ID order. A task may be late (if an earlier task is still running) but never early.
s = DelayedScheduler()
log = []
s.schedule(lambda: log.append("slow"), 300) # 1
s.schedule(lambda: log.append("fast"), 50) # 2
s.schedule(lambda: log.append("mid"), 150) # 3
s.pending() # 3
s.shutdown(wait=True) # returns after ~300 ms
log # ["fast", "mid", "slow"]
The trap: after the thread starts sleeping until the task that's due first, a new, earlier task may arrive. The thread must wake up and re-plan, otherwise a 50 ms task scheduled just after a 10-second one would wait 10 seconds. Don't poll in a loop with sleep either.
A task may itself call schedule. Never hold your lock while running a task.
Hint: The background thread should never sleep past the earliest due time it knows about, and must re-check the heap whenever it wakes, because it may have been woken by an earlier arrival rather than by its timeout.
Show hint
Keep the pending tasks in a heap of (due_time, id, task) (using time.monotonic()), guarded by one threading.Condition that schedule notifies after each push.