Overview

The sched module implements a generic event scheduler for running tasks at specific times. The scheduler class uses a time function to learn the current time, and a delay function to wait for a specific period of time. The actual units of time are not important, which makes the interface flexible enough to be used for many purposes.

The time function is called without any arguments, and should return a number representing the current time. The delay function is called with a single integer argument, using the same scale as the time function, and should wait that many time units before returning. For example, the time.time() and time.sleep() functions meet these requirements.

To support multi-threaded applications, the delay function is called with argument 0 after each event is generated, to ensure that other threads also have a chance to run.

Running Events With a Delay

Events can be scheduled to run after a delay, or at a specific time. To schedule them with a delay, use the enter() method, which takes 4 arguments:

  • A number representing the delay
  • A priority value
  • The function to call
  • A tuple of arguments for the function

This example schedules 2 different events to run after 2 and 3 seconds respectively. When the event’s time comes up, print_event() is called and prints the current time and the name argument passed to the event.

import sched, time

scheduler = sched.scheduler(time.time, time.sleep)

def print_event(name):
    print('EVENT:', time.time(), name)

print('START:', time.time())
scheduler.enter(2, 1, print_event, argument=('first',))
scheduler.enter(3, 1, print_event, argument=('second',))

scheduler.run()

#output should be like this
START: 1563132026.4948053
EVENT: 1563132036.9802392 first
EVENT: 1563132036.980326 second

The time printed for the first event is 2 seconds after start, and the time for the second event is 3 seconds after start.

Overlapping Events

The call to run() blocks until all of the events have been processed. Each event is run in the same thread, so if an event takes longer to run than the delay between events, there will be overlap. The overlap is resolved by postponing the later event.

No events are lost, but some events may be called later than they were scheduled. In the next example, long_event() sleeps but it could just as easily delay by performing a long calculation or by blocking on I/O.

import sched, time

scheduler = sched.scheduler(time.time, time.sleep)

def long_event(name):
    print('BEGIN EVENT :', time.time(), name)
    time.sleep(2)
    print('FINISH EVENT:', time.time(), name)

print('START:', time.time())
scheduler.enter(2, 1, long_event, argument=('first',))
scheduler.enter(3, 1, long_event, argument=('second',))

scheduler.run()

The result is the second event is run immediately after the first finishes, since the first event took long enough to push the clock past the desired start time of the second event.

#output
START: 1361446602.55
BEGIN EVENT : 1361446604.55 first
FINISH EVENT: 1361446606.55 first
BEGIN EVENT : 1361446606.55 second
FINISH EVENT: 1361446608.55 second

Event Priorities

If more than one event is scheduled for the same time their priority values are used to determine the order they are run.

import sched, time

scheduler = sched.scheduler(time.time, time.sleep)

def print_event(name):
    print('EVENT:', time.time(), name)

now = time.time()
print('START:', now)
scheduler.enterabs(now+2, 2, print_event, argument=('first',))
scheduler.enterabs(now+2, 1, print_event, argument=('second',))

scheduler.run()

This example needs to ensure that they are scheduled for the exact same time, so the enterabs() method is used instead of enter(). The first argument to enterabs() is the time to run the event, instead of the amount of time to delay.

#output

START: 1361446608.62
EVENT: 1361446610.62 second
EVENT: 1361446610.62 first

Canceling Events

Both enter() and enterabs() return a reference to the event which can be used to cancel it later. Since run() blocks, the event has to be canceled in a different thread. For this example, a thread is started to run the scheduler and the main processing thread is used to cancel the event.

import sched
import threading
import time

scheduler = sched.scheduler(time.time, time.sleep)

# Set up a global to be modified by the threads
counter = 0

def increment_counter(name):
    global counter
    print('EVENT:', time.time(), name)
    counter += 1
    print('NOW:', counter)

print('START:', time.time())
e1 = scheduler.enter(2, 1, increment_counter, ('E1',))
e2 = scheduler.enter(3, 1, increment_counter, ('E2',))

# Start a thread to run the events
t = threading.Thread(target=scheduler.run)
t.start()

# Back in the main thread, cancel the first scheduled event.
scheduler.cancel(e1)

# Wait for the scheduler to finish running in the thread
t.join()

print('FINAL:', counter)

Two events were scheduled, but the first was later canceled. Only the second event runs, so the counter variable is only incremented one time.

#output
START: 1361446610.65
EVENT: 1361446613.66 E2
NOW: 1
FINAL: 1

Scheduler run method

Run all scheduled events. This method will wait (using the delayfunc() function passed to the constructor) for the next event, then execute it and so on until there are no more scheduled events.

If blocking is false executes the scheduled events due to expire soonest (if any) and then return the deadline of the next scheduled call in the scheduler (if any).

Either action or delayfunc can raise an exception. In either case, the scheduler will maintain a consistent state and propagate the exception. If an exception is raised by action, the event will not be attempted in future calls to run().

If a sequence of events takes longer to run than the time available before the next event, the scheduler will simply fall behind. No events will be dropped; the calling code is responsible for canceling events which are no longer pertinent.