Periodic execution with asyncio

Terry Reedy tjreedy at udel.edu
Fri Nov 22 19:00:17 EST 2013


On 11/22/2013 4:30 PM, Tobias M. wrote:

> I am using the asyncio package (Codename 'Tulip'), which will be
> available in Python 3.4, for the first time.

Great. New stuff, both behavior and API, needs to be 'exercised', 
especially by non-experts in the subject.  I have no experience with 
async stuff either.

> I want the event loop to run a function periodically (e.g. every 2
> seconds). PEP 3156 suggests two ways to implement such a periodic call:

The PEP is still the doc, and the Library Manual entry is a stub 
pointing back to it.
http://python.org/dev/peps/pep-3156/

> 1. Using a callback that reschedules itself, using call_later().
> 2. Using a coroutine containing a loop and a sleep() call.
>
> I implemented the first approach in a class with an easy to use
> interface. It can be subclassed and the run() method
> can be overwritten to provide the code that will be called periodically.
> The interval is specified in the constructor and the task can be started
> and stopped:
>
> import asyncio
>
> class PeriodicTask(object):
>
>      def __init__(self, interval):
>          self._interval = interval
>          self._loop = asyncio.get_event_loop()
>
>      def _run(self):
>          self.run()
>          self._handler = self._loop.call_later(self._interval, self._run)
>
>      def run(self):
>          print('Hello World')
>
>      def start(self):
>          self._handler = self._loop.call_later(self._interval, self._run)
>
>      def stop(self):
>          self._handler.cancel()
>
>
> To run this task and execute run() every 2 seconds you can do:
>
> task = PeriodicTask(2)
> task.start()
> asyncio.get_event_loop().run_forever()

> 1. Is this a reasonable implementation or is there anything to improve?

It may be both reasonable and subject to change. Here is my version, 
with the following changes

* Make the task function a parameter 'func'.

* Rename start to _set to better describe what is does and call it in 
the _run function to not repeat the handler setting line. (If 'start' 
has any magic meaning, I am not aware of it. I am aware that there could 
be situations in which one would want a public method, to be called from 
other handlers. But that would be a different class ;-)

* Fully initialize the instance in __init__ by calling _set.

* Provide a clean way to stop the event loop without a traceback (or 
using Windows Task Manager.

* Comment out the stop method because it is useless in the current 
usage. I believe handler.cancel is only relevant after the handler is 
set and before it is fired. This can only be done from another handler.

import asyncio

class PeriodicTask(object):
      def __init__(self, func, interval):
          self.func = func
          self.interval = interval
          self._loop = asyncio.get_event_loop()
          self._set()
      def _set(self):
          self._handler = self._loop.call_later(self.interval, self._run)
      def _run(self):
          self.func()
          self._set()

##     def stop(self):
##         self._handler.cancel()

def f():
     print('Hello World')

task = PeriodicTask(f, 2)
try:
     asyncio.get_event_loop().run_forever()
except KeyboardInterrupt:
     print('Loop stopped')

> 2. How would you implement the second approach from the PEP (using a
> coroutine) with the same interface as my PeriodicTask above?

Theoretically, by Guido's rationale, that should be easier (for me, at 
least), since it would be more like the kind of Python code I already write.

> I tried the second approach but wasn't able to come up with a solution,
> as I was too confused by the concepts of coroutines, Tasks, etc.

I will try to look at the PEP and see how it works for me.

-- 
Terry Jan Reedy




More information about the Python-list mailing list