8

I've been using asyncio for a bit but I'm still fairly unfamiliar with it. My current issue is that while trying to wait for a response from a function with asyncio, the waiting (while loop) blocks the function from happening. Here is the code that sums up the problem:

import asyncio

response = 0

async def handle(x):
    await asyncio.sleep(0.1)
    return x

async def run():
    global response
    for number in range(1, 21):
        response = await handle(number)
        print(response)
        if response == 10:
            await wait_for_next(response)

async def wait_for_next(x):
    while response == x:
        print('waiting',response,x)
        await asyncio.sleep(0.5)
    print('done')

tasks = [run()]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

wait_for_next is supposed to wait for the next response, but the while loop blocks the run() function. How could I stop this happening? Should I be using loop.run_in_executor, and if so, how?

(There were a couple of other examples of this I could find, but they were very specific and I didn't understand if our problems/solutions would be the same.)

ShuckleShackle
  • 97
  • 1
  • 2
  • 7
  • I didn't use asyncio before, but I worked with async programming. the while loop will block the current thread, If you need execute a blocking functions you can use the run_in_executor() method of the EventLoop, this will be run the function in an executor. loop.run_in_executor(executor=None, fn, *args) – Fady Saad Sep 29 '17 at 22:19
  • It is blocked because there is nothing else to do. There is only one task to execute. May be add another task to tasks and the wait would be interleaved – balki Sep 29 '17 at 22:21
  • 1
    Thanks @balki , it seems like adding another task allowed it to continue, so I'll have to split my run() task into seperate parts. – ShuckleShackle Sep 29 '17 at 22:36

1 Answers1

10

As already noted, loop stuck because await wait_for_next(response) blocks execution flow until this coroutine wouldn't be finished.

If you want some of your coroutines to be started without blocking execution flow you can start it as asyncio.Task (more about tasks) using ensure_future function:

import asyncio

response = 0

async def handle(x):
    await asyncio.sleep(0.1)
    return x

async def run():
    global response
    for number in range(1, 21):
        response = await handle(number)
        print(response)
        if response == 10:

            # run wait_for_next "in background" instead of blocking flow:
            asyncio.ensure_future(wait_for_next(response))

async def wait_for_next(x):
    while response == x:
        print('waiting',response,x)
        await asyncio.sleep(0.5)
    print('done')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run())

Output:

1
2
3
4
5
6
7
8
9
10
waiting 10 10
11
12
13
14
done
15
16
17
18
19
20
Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • This also works, thanks! Now I'll have to decide which correction benefits the program more 3: – ShuckleShackle Sep 30 '17 at 10:40
  • Just remember to await on asyncio.sleep() in your background while loop, so other coroutines can be executed too. – extraymond Jan 04 '19 at 15:59
  • Does not use `global` variable breaks the reason for using asyncio? I can set a `global` variable inside any loop and stop it without using asyncio. Am I right? Maybe there are any other solutions? – Nairum Sep 25 '19 at 15:26
  • 1
    PS: in Python 3.7+ use `create_task()` instead of `ensure_future()` https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task – Nairum Sep 25 '19 at 15:32