avenmia
avenmia

Reputation: 2605

How to pause an asyncio created task using a lock or some other method?

This is a sample program of something I'm trying to do in a main project, but the main idea is that I created a task using a asyncio.create_task. I want to then pause that task later on in the code when some event happens. In this sample code, asyncio.create_task starts task_tic_tock and prints "tic-tock". Later in the code, when other_task has a count that is divisible by 3, it takes a lock and starts task_tic_tock with the parameter being False. I'm not sure a lock is the best thing to use, but the behavior I am hoping for is for it to print "In new count" and "print val is now false", but not "tic-"tock". Once the lock ends I want it to resume printing "tic_tock".

import asyncio

lock = asyncio.Lock()

def task_tic_tock(print_val):
    if print_val:
        print("tic-tock")
    else:
        print("Print val is now false")

async def start_tic_tock(print_val):
    while True:
        task_tic_tock(print_val)
        await asyncio.sleep(1)

async def other_task():
    count = 0
    while True:
        count = count + 1
        if count % 3 == 0:
            async with lock:
                task_tic_tock(False)
                new_count = 5
                while new_count > 0:
                    print("In new count")
                    new_count = new_count - 1
                    await asyncio.sleep(1)
        await asyncio.sleep(1)
    print("Other task running")
    await asyncio.sleep(1)

async def main():
    print_val = asyncio.create_task(start_tic_tock(True))
    await asyncio.gather(other_task(), print_val)


asyncio.run(main())

How does create_task work and is it possible to pause it for a period of time while another task is going on? Any help would be appreciated!

Upvotes: 1

Views: 4569

Answers (1)

Matt Fowler
Matt Fowler

Reputation: 2733

You can achieve what you want with a lock. The issue with your code is that you never acquire the lock in start_tic_tock, so you have no way to 'pause' that coroutine based on the lock you acquire in other_task.

You can fix this by making your start_tic_tock method acquire the lock as follows:

async def start_tic_tock(print_val):
   while True:
       async with lock:
           task_tic_tock(print_val)
           await asyncio.sleep(1)

That said an Event may be better suited to what you want. Events are explicitly for notifying tasks when a condition is achieved. Full code is below using an event. Note that when you create an Event or a Lock globally as you do in the above code you might be creating a new event loop. asyncio.run always creates a new loop which means you could have two loops running and this can lead to exceptions. In the below I've explicitly created a loop to avoid these errors, you'll need to refactor a bit to use asyncio.run again.

import asyncio

loop = asyncio.new_event_loop()
event = asyncio.Event(loop=loop)

async def start_tic_tock(print_val):
    while True:
        await event.wait()
        task_tic_tock(print_val)
        await asyncio.sleep(1)

async def other_task():
    count = 0
    while True:
        count = count + 1
        if count % 3 == 0:
            event.clear()
            task_tic_tock(False)
            new_count = 5
            while new_count > 0:
                print("In new count")
                new_count = new_count - 1
                await asyncio.sleep(1)
            event.set()
        await asyncio.sleep(1)
    print("Other task running")
    await asyncio.sleep(1)

async def main():
    print_val = asyncio.create_task(start_tic_tock(True))
    await asyncio.gather(other_task(), print_val)

loop.run_until_complete(main())

The above prints:

Print val is now false
In new count
In new count
In new count
In new count
In new count
tic-tock
tic-tock
tic-tock

Upvotes: 3

Related Questions