0

Let's say I have a background task that's looping through existing orders (doing some manipulations if necessary). Now if I want to add an order, this background task should stop while I'm placing a new order and resume when I am finished.

In pseudo-code:

async def loop_orders():
    while True:
        do_this()
        do_that()
    return

async def create_order():
    stop_loop_orders()
        ...
        send_order()
    resume_loop_orders()
    return

I couldn't figure out which of these two is the way to go:

  • asyncio.Lock: What is locked? How do I define what should be locked?
  • asyncio.Event: The wait() method would "Wait until the event is set." => but I would rather need a "wait if the event is set" functionality.
Joël Brigate
  • 237
  • 2
  • 13
  • when you start create_order then you should set value in `lock` and unset it at the end of `create_order`. And `loop_order` should always check this `lock` and skipe functions: `if not lock: do_this() do_that()` – furas Nov 02 '21 at 20:42
  • with `Event` you would think in different way: instead of `wait if the event is set` you can use `wait if the event is **unset**/**clear**` but I don't know if it will work as expected. – furas Nov 02 '21 at 21:11
  • Does this answer your question? [Is it possible to suspend and restart tasks in async python?](https://stackoverflow.com/questions/66687549/is-it-possible-to-suspend-and-restart-tasks-in-async-python) – thisisalsomypassword Nov 03 '21 at 15:53
  • Thanks for your hints. @VPfB's example helped me to solve my issue. – Joël Brigate Nov 05 '21 at 11:34

3 Answers3

1

My first idea was

lock = asyncio.Lock()

async def loop_orders():
    while True:
        if not lock.locked():
            do_this()
            do_that()

async def create_order():
    async with lock:
        send_order()

So before sending order it sets lock so if not lock.locked(): should skip do_this() do_that(). But code can run somewhere inside do_this() do_that() and then lock.locked() can't stop it. So maybe it should be in both

lock = asyncio.Lock()

async def loop_orders():
    while True:
        async with lock:
            do_this()
            do_that()

async def create_order():
    async with lock:
        send_order()

This way when it starts sending then it has to wait with do_this() do_that(). And when it starts do_this() do_that() then it has to wait with send_order()


BTW:

If you want to understand it then in normal code it could look like

lock = False

async def loop_orders():
    global lock

    while True:

        # wait if `lock` was set `True` in other function(s)
        while lock:         
            pass

        # set `True` to block other function(s)
        lock = True

        do_this()
        do_that()

        # set `False` to unblock other function(s)
        lock = False

async def create_order():
    global lock

    # wait if `lock` was set `True` in other function(s)
    while lock:
        pass

    # set `True` to block other function(s)
    lock = True

    send_order()

    # set `False` to unblock other function(s)
    lock = False

BTW:

I think you could use Event in similar way but you should think in different way: instead of wait if the event is set you can use wait if the event is **unset**/**clear**

furas
  • 134,197
  • 12
  • 106
  • 148
  • Task C (create) should block the task L (loop). But the two tasks have 50:50 chance to get the lock. It means that often the task L blocks the task C. Not sure the OP wanted that. – VPfB Nov 02 '21 at 21:18
  • @VPfB I don't have full working code to test if my idea is good. It was only some idea to show direction but not full solution. – furas Nov 02 '21 at 21:22
1

You cannot suspend and resume an asyncio task.

You could cancel the task and later create a new one, but this leads to more problems than it solves. Data consistency may be compromised and the new task will not resume exactly where the previous one was interupted.

You could easily make the task wait at some specific point (or points)

async def loop_orders():
    while True:
        ... wait here while paused ...
        do_this()
        do_that()

but when the create_order pauses the loop_orders task, the former must wait until the latter reaches that point where it pauses - the create_order task requests a pause and the loop_orders acknowledges.

I made a little demo with two Events that I named enable and idle in an attempt to make the method names .clear, .set and .wait match the logic.

import asyncio

enable = None
idle = None

async def loop_orders():
    while True:
        idle.set()
        await enable.wait()
        idle.clear();
        print("processing order ... ", end="", flush=True)
        await asyncio.sleep(0.7)
        print("done")

async def create_order():
    enable.clear();
    await idle.wait()
    print("-- pause start ... ", end="", flush=True)
    await asyncio.sleep(2)
    print("stop")
    enable.set()

async def test():
    global enable, idle

    enable = asyncio.Event()
    enable.set()    # initial state = enabled
    idle = asyncio.Event()

    asyncio.create_task(loop_orders())
    await asyncio.sleep(2)
    await create_order()
    await asyncio.sleep(2)
    await create_order()
    await asyncio.sleep(1)
    print("EXIT")


asyncio.run(test())
VPfB
  • 14,927
  • 6
  • 41
  • 75
0

First of all, I think you should maybe consider implementing this as a producer/consumer pattern using a Queue.

But if you really want to suspend the execution of a Task, you can take a look at this question.

thisisalsomypassword
  • 1,423
  • 1
  • 6
  • 17