]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """A generally useful event scheduler class.\r |
2 | \r | |
3 | Each instance of this class manages its own queue.\r | |
4 | No multi-threading is implied; you are supposed to hack that\r | |
5 | yourself, or use a single instance per application.\r | |
6 | \r | |
7 | Each instance is parametrized with two functions, one that is\r | |
8 | supposed to return the current time, one that is supposed to\r | |
9 | implement a delay. You can implement real-time scheduling by\r | |
10 | substituting time and sleep from built-in module time, or you can\r | |
11 | implement simulated time by writing your own functions. This can\r | |
12 | also be used to integrate scheduling with STDWIN events; the delay\r | |
13 | function is allowed to modify the queue. Time can be expressed as\r | |
14 | integers or floating point numbers, as long as it is consistent.\r | |
15 | \r | |
16 | Events are specified by tuples (time, priority, action, argument).\r | |
17 | As in UNIX, lower priority numbers mean higher priority; in this\r | |
18 | way the queue can be maintained as a priority queue. Execution of the\r | |
19 | event means calling the action function, passing it the argument\r | |
20 | sequence in "argument" (remember that in Python, multiple function\r | |
21 | arguments are be packed in a sequence).\r | |
22 | The action function may be an instance method so it\r | |
23 | has another way to reference private data (besides global variables).\r | |
24 | """\r | |
25 | \r | |
26 | # XXX The timefunc and delayfunc should have been defined as methods\r | |
27 | # XXX so you can define new kinds of schedulers using subclassing\r | |
28 | # XXX instead of having to define a module or class just to hold\r | |
29 | # XXX the global state of your particular time and delay functions.\r | |
30 | \r | |
31 | import heapq\r | |
32 | from collections import namedtuple\r | |
33 | \r | |
34 | __all__ = ["scheduler"]\r | |
35 | \r | |
36 | Event = namedtuple('Event', 'time, priority, action, argument')\r | |
37 | \r | |
38 | class scheduler:\r | |
39 | def __init__(self, timefunc, delayfunc):\r | |
40 | """Initialize a new instance, passing the time and delay\r | |
41 | functions"""\r | |
42 | self._queue = []\r | |
43 | self.timefunc = timefunc\r | |
44 | self.delayfunc = delayfunc\r | |
45 | \r | |
46 | def enterabs(self, time, priority, action, argument):\r | |
47 | """Enter a new event in the queue at an absolute time.\r | |
48 | \r | |
49 | Returns an ID for the event which can be used to remove it,\r | |
50 | if necessary.\r | |
51 | \r | |
52 | """\r | |
53 | event = Event(time, priority, action, argument)\r | |
54 | heapq.heappush(self._queue, event)\r | |
55 | return event # The ID\r | |
56 | \r | |
57 | def enter(self, delay, priority, action, argument):\r | |
58 | """A variant that specifies the time as a relative time.\r | |
59 | \r | |
60 | This is actually the more commonly used interface.\r | |
61 | \r | |
62 | """\r | |
63 | time = self.timefunc() + delay\r | |
64 | return self.enterabs(time, priority, action, argument)\r | |
65 | \r | |
66 | def cancel(self, event):\r | |
67 | """Remove an event from the queue.\r | |
68 | \r | |
69 | This must be presented the ID as returned by enter().\r | |
70 | If the event is not in the queue, this raises ValueError.\r | |
71 | \r | |
72 | """\r | |
73 | self._queue.remove(event)\r | |
74 | heapq.heapify(self._queue)\r | |
75 | \r | |
76 | def empty(self):\r | |
77 | """Check whether the queue is empty."""\r | |
78 | return not self._queue\r | |
79 | \r | |
80 | def run(self):\r | |
81 | """Execute events until the queue is empty.\r | |
82 | \r | |
83 | When there is a positive delay until the first event, the\r | |
84 | delay function is called and the event is left in the queue;\r | |
85 | otherwise, the event is removed from the queue and executed\r | |
86 | (its action function is called, passing it the argument). If\r | |
87 | the delay function returns prematurely, it is simply\r | |
88 | restarted.\r | |
89 | \r | |
90 | It is legal for both the delay function and the action\r | |
91 | function to to modify the queue or to raise an exception;\r | |
92 | exceptions are not caught but the scheduler's state remains\r | |
93 | well-defined so run() may be called again.\r | |
94 | \r | |
95 | A questionable hack is added to allow other threads to run:\r | |
96 | just after an event is executed, a delay of 0 is executed, to\r | |
97 | avoid monopolizing the CPU when other threads are also\r | |
98 | runnable.\r | |
99 | \r | |
100 | """\r | |
101 | # localize variable access to minimize overhead\r | |
102 | # and to improve thread safety\r | |
103 | q = self._queue\r | |
104 | delayfunc = self.delayfunc\r | |
105 | timefunc = self.timefunc\r | |
106 | pop = heapq.heappop\r | |
107 | while q:\r | |
108 | time, priority, action, argument = checked_event = q[0]\r | |
109 | now = timefunc()\r | |
110 | if now < time:\r | |
111 | delayfunc(time - now)\r | |
112 | else:\r | |
113 | event = pop(q)\r | |
114 | # Verify that the event was not removed or altered\r | |
115 | # by another thread after we last looked at q[0].\r | |
116 | if event is checked_event:\r | |
117 | action(*argument)\r | |
118 | delayfunc(0) # Let other threads run\r | |
119 | else:\r | |
120 | heapq.heappush(q, event)\r | |
121 | \r | |
122 | @property\r | |
123 | def queue(self):\r | |
124 | """An ordered list of upcoming events.\r | |
125 | \r | |
126 | Events are named tuples with fields for:\r | |
127 | time, priority, action, arguments\r | |
128 | \r | |
129 | """\r | |
130 | # Use heapq to sort the queue rather than using 'sorted(self._queue)'.\r | |
131 | # With heapq, two events scheduled at the same time will show in\r | |
132 | # the actual order they would be retrieved.\r | |
133 | events = self._queue[:]\r | |
134 | return map(heapq.heappop, [events]*len(events))\r |