DJSDev
DJSDev

Reputation: 993

Not sure why async tasks are completing outside of event loop

I cannot figure out this behavior. I'm using Python 3.6.4.

In here, I have 4 async functions. In test1, I call asyncio.ensure_future(TestAsync()) and turn the coroutine into a task. On the others (test2, test3, test4) I didn't call ensure_future(), I just created the coroutine and passed them into a list and then passed the list into the event loop.

I left test1 out of the list I passed into the event loop. So I was under the impression test1 would not run, but in my output, it clearly did. Can someone explain to me why test1 still outputed when it wasn't inside the event loop?

import asyncio
import random

async def TestAsync():
    print("Test 1 started")
    wait = random.randint(1, 10)
    await asyncio.sleep(wait)
    print("Test 1 done awaited: " + str(wait))

async def TestAsync2():
    print("Test 2 started")
    wait = random.randint(1, 10)
    await asyncio.sleep(wait)
    print("Test 2 done awaited: " + str(wait))

async def TestAsync3():
    print("Test 3 started")
    wait = random.randint(1, 10)
    await asyncio.sleep(wait)
    print("Test 3 done awaited: " + str(wait))

async def TestAsync4():
    print("Test 4 started")
    wait = random.randint(1, 10)
    await asyncio.sleep(wait)
    print("Test 4 done awaited: " + str(wait))

test1 = asyncio.ensure_future(TestAsync())
test2 = TestAsync2()
test3 = TestAsync3()
test4 = TestAsync4()
tasklist = [test2, test3, test4]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasklist))

Output

Test 1 started.    <--This is the one wasn't in the event loop
Test 3 started
Test 4 started
Test 2 started
Test 4 done awaited: 1
Test 3 done awaited: 2
Test 1 done awaited: 7
Test 2 done awaited: 10

Upvotes: 2

Views: 1666

Answers (3)

user4815162342
user4815162342

Reputation: 155046

As others pointed out, ensure_future will add the task to the default event loop, to run at the next opportunity. ensure_future converts an arbitrary awaitable into an asyncio.Future, which in case of a coroutine object is accomplished by wrapping it into a Task (a subclass of Future) with a call to create_task. The task will run the next time the event loop spins regardless of whether the code that calls ensure_future stores a reference to the returned future.

The other important thing to notice is that run_until_complete(x) means "submit x to the event loop and run the loop until x completes", it does nothing to prevent tasks added before the call to run_until_complete from running.

In Python 3.7, there is a new function asyncio.run that creates a new event loop and submits the given coroutine to it. Replacing asyncio.run_until_complete with asyncio.run would produce the behavior you expect.

Upvotes: 3

Mad Physicist
Mad Physicist

Reputation: 114330

From the docs:

Calling a coroutine does not start its code running – the coroutine object returned by the call doesn’t do anything until you schedule its execution. There are two basic ways to start it running: call await coroutine or yield from coroutine from another coroutine (assuming the other coroutine is already running!), or schedule its execution using the ensure_future() function or the AbstractEventLoop.create_task() method.

As @dirn's answer implies, ensure_future by itself does not start the task unless there is an event loop already running. However, the subsequent loop kickoff with run_until_complete does start the task.

Upvotes: 0

dirn
dirn

Reputation: 20739

From the docs for ensure_future:

Schedule the execution of a coroutine object

When you call ensure_future, the coroutine is schedule and will be eligible to run whenever the event loop is running.

Upvotes: 0

Related Questions