8

I'd like to understand how to use the optional parameter blocking in the method scheduler.run(blocking=True). Any practical/real-world example would be very helpful.

Based on the research I've done so far, the intention of the blocking optional argument is for non-blocking or async applications[1][2]. Below is the main run loop of the schduler (from the python 3.6 library sched.py). Following through the code, I notice that whenever blocking is set to False, immediately returns the time difference between target time and current time, unless the target time had passed, in which case the action would be executed.

while True:
    with lock:
        if not q:
            break
        time, priority, action, argument, kwargs = q[0]
        now = timefunc()
        if time > now:
            delay = True
        else:
            delay = False
            pop(q)
    if delay:
        if not blocking:
            return time - now
        delayfunc(time - now)
    else:
        action(*argument, **kwargs)
        delayfunc(0)   # Let other threads run

Seems to me the non-blocking design requires me to keep running the scheduler until the queue is clean. Thus, I'm thinking about maintaining a task queue myself and keep pushing the scheduler.run task into the queue (like the code below.) Is this a desirable design? What is the proper way of using the non-blocking scheduler?

def action():
    print('action at: ', datetime.now())

if __name__ == '__main__':
    s = sched.scheduler(time.time)
    target_time = datetime.now() + timedelta(seconds=5)
    s.enterabs(target_time.timestamp(), 1, action)
    run = functools.partial(s.run, blocking=False)
    taskq = deque()
    taskq.append(run)
    while taskq:
        task = taskq.popleft()
        result = task()
        print(result)
        if result:
            taskq.append(run)
            time.sleep(1)

    print('end tasks')

[1] What’s New In Python 3.3

[2] Issue13449: sched - provide an "async" argument for run() method

dawranliou
  • 141
  • 2
  • 8

1 Answers1

5

Old question, but I just implemented something which used the nonblocking version pretty effectively.

When blocking = True in sched.scheduler.run, it will call the delayfunc for the time difference until the next event.

This may be undesirable if your application, at t = 0, schedules an event A for t = 10, but another thread, at t = 1, schedules an event B for t = 5. In this case,

s = sched.scheduler(time.time)
# Spawn threads which enter A and B into s
while True:
    s.run(True)

if your main thread is just calling sched.scheduler.run(blocking=True) in a loop, at t = 0 it will call delayfunc(10) because it only sees that it has 10 time units left until A. The main thread won't wake up until t = 10, at which point it will see that it missed B, run B 5 time units late, and then run A after B.

To solve this, you can change the main thread to look like this:

s = sched.scheduler(time.time)
# Spawn threads which enter A and B into s
while True:
    next_ev = s.run(False)
    if next_ev is not None:
        time.sleep(min(1, next_ev))
    else:
        time.sleep(1)

This code will catch up on all present events, then sleep until the next event, or if there is no next event or if the next event is too far ahead, will sleep for 1 second.

Ideally, scheduler would be implemented with a condition variable for if a new event reaches the front of the priority queue, and it could wait on that variable instead of just sleeping until the next event. This would be the most efficient and most time accurate.

bathtub
  • 426
  • 3
  • 15
  • Your premise is wrong. The first event to run will be the one closest to `t0`. The order in which you `enter()` the events doesn't matter. Try this to check this is true: `import sched, time; s = sched.scheduler(time.time, time.sleep); s.enter(10, 0, print, argument=('test',)); s.enter(5, 0, print, argument=('test2',)); s.run()`. You will see `test2` is printed 5 seconds before `test`. It wouldn't be much of a scheduler otherwise ;) – Gonçalo Ribeiro Jan 06 '19 at 20:17
  • This is a single threaded example. By waiting to run, it knows when the next event is before it starts to sleep. The issue is that if you enter the second event after doing a blocking run in another thread, it will sleep until the first event, regardless of events you schedule before then after the run starts. – bathtub Jan 10 '19 at 19:39
  • 1
    @bathtub Calling s.run(blocking=False) and sleeping at most 1 second helps me to solve the problem when using main thread to while-loop run() and another thread to add event targeting to happen shortly. I am expecting the more efficient version of it using condition variable instead of waking up every second. – etoricky Jul 16 '19 at 07:27