2

I have a service running the following loop

while True:
    feedback = f1()
    if check1(feedback):
        break

    feedback = f2()
    if check2(feedback):
        break

    feedback = f3()
    if check3(feedback):
        break

    time.sleep(10)

do_cleanup(feedback)

Now I would like to run these feedback checks with different time intervals. One naive way is to move the time.sleep() into the f functions. But that causes blocking. What would be the easiest way to achieve periodic checks with different intervals? Here all the f functions are cheap to run.

The event loop in asyncio sounds like the way to go. But due to my inexperience, I don't know where the check and break logic should go for the event loop.

Or is there any other packages/code patterns to do this kind of monitoring logic?

1 Answer 1

2

In asyncio you might split the service into three separate tasks, each with its own loop and timing - you can think of them as three threads, except they are all scheduled in the same thread, and multi-task cooperatively by suspending at await.

For this purpose let's start with a utility function that calls a function and checks its result at a regular interval:

async def at_interval(f, check, seconds):
    while True:
        feedback = f()
        if check(feedback):
            return feedback
        await asyncio.sleep(seconds)

The return is the equivalent to the break in your original code.

With that in place, the service spawns three such loops and wait for any of them to finish. Whichever completes first carries the "feedback" we're waiting for, and we can dispose of the others.

async def service():
    loop = asyncio.get_event_loop()
    t1 = loop.create_task(at_interval(f1, check1, 3))
    t2 = loop.create_task(at_interval(f2, check2, 5))
    t3 = loop.create_task(at_interval(f3, check3, 7))
    done, pending = await asyncio.wait(
        [t1, t2, t3], return_when=asyncio.FIRST_COMPLETED)
    for t in pending:
        t.cancel()
    feedback = await list(done)[0]
    do_cleanup(feedback)

asyncio.get_event_loop().run_until_complete(service())

A small difference between this and your code is that here it is possible (though very unlikely) for more than one check to fail before the service picks up on it. For example, if through a stroke of bad luck two of the above tasks end up sharing the absolute time of wakeup to the microsecond, they will be scheduled in the same event loop iteration. Both will return from their corresponding at_interval coroutines, and done will contain more than one feedback. The code handles it by picking a feedback and calling do_cleanup on that one, but it could also loop over all.

If this is not acceptable, you can easily pass each at_interval a callable that cancels all tasks except itself. This is currently done in service for brevity, but it can be done in at_interval as well. One task cancelling the others would ensure that only one feedback can exist.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.