Nyx
Nyx

Reputation: 13

Can python's sched module run asynchronous tasks without blocking during a task execution?

I have a Module class, with an invoke() method, which may execute some IO-bound/wait.

I use sched to run multiple instances of said class at different intervals, in a way that may make a long-running call overlap with another task.

Can the sched module spawn different tasks and work with them in a non-blocking way, and if not, which module may do the job for multiple repetitive concurrent tasks?

I looked into sched but couldn't find any definitive answer. I also looked into asyncio, which prompted me with two answers that don't seem to fit my needs, respectively while True (which seems cumbersome) and Tasks (which doesn't appear to allow to run multiple methods concurrently).

Upvotes: 0

Views: 1316

Answers (1)

BramAppel
BramAppel

Reputation: 1366

Study the code below, chapter 18 from Fluent Python by Luciano Ramalho is an excellent resource on the asyncio module. Also, never forget the docs itself at https://docs.python.org/3/library/asyncio.html

#!/usr/bin/env python3

# spinner_asyncio.py

# credits: Example by Luciano Ramalho inspired by
# Michele Simionato's multiprocessing example in the python-list:
# https://mail.python.org/pipermail/python-list/2009-February/538048.html

import asyncio
import itertools
import sys


@asyncio.coroutine  # <1>
def spin(msg):  
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):
        status = char + ' ' + msg
        write(status)
        flush()
        write('\x08' * len(status))
        try:
            yield from asyncio.sleep(.1)  # <3>
        except asyncio.CancelledError:  # <4>
            break
    write(' ' * len(status) + '\x08' * len(status))


@asyncio.coroutine
def slow_function():  # <5>
    # pretend waiting a long time for I/O, Non-blocking call!
    yield from asyncio.sleep(3)  # <6>
    return 42


@asyncio.coroutine
def supervisor():  # <7>
    spinner = asyncio.async(spin('thinking!'))  # <8>
    print('spinner object:', spinner)  # <9>
    result = yield from slow_function()  # <10>
    spinner.cancel()  # <11>
    return result


def main():
    loop = asyncio.get_event_loop()  # <12>
    result = loop.run_until_complete(supervisor())  # <13>
    loop.close()
    print('Answer:', result)


if __name__ == '__main__':
    main()

1: Coroutines intended for use with asyncio should be decorated with @asyn cio.coroutine. This not mandatory, but is highly advisable.

3: Use yield from asyncio.sleep(.1) instead of just time.sleep(.1), to sleep without blocking the event loop.

4: If asyncio.CancelledError is raised after spin wakes up, it’s because cancellation was requested, so exit the loop.

5: slow_function is a coroutine, and uses yield from to let the event loop proceed while this coroutine pretends to do I/O by sleeping.

6: The yield from asyncio.sleep(3) expression handles the control flow to the main loop, which will resume this coroutine after the sleep delay.

7: supervisor is a coroutine as well, so it can drive slow_function with yield from.

8: asyncio.async(…) schedules the spin coroutine to run, wrapping it in a Task object, which is returned immediately

9: Display the Task object. The output looks like Task pending coro= spin() running at spinner_asyncio.py:12.

10: Drive the slow_function(). When that is done, get the returned value. Meanwhile, the event loop will continue running because slow_function ultimately uses yield from asyncio.sleep(3) to hand control back to the main loop.

11: A Task object can be cancelled; this raises asyncio.CancelledError at the yield line where the coroutine is currently suspended. The coroutine may catch the exception and delay or even refuse to cancel.

12: Get a reference to the event loop.

13: Drive the supervisor coroutine to completion; the return value of the coroutine is the return value of this call.

Upvotes: 2

Related Questions