最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Asyncio future, running Future objects - Stack Overflow

programmeradmin4浏览0评论

I have a code:

import asyncio as aio


async def coro(future: aio.Future):
    print('Coro start')
    await aio.sleep(3)
    print('Coro finish')
    future.set_result('coro result')


async def main():
    future = aio.Future()
    aio.create_task(coro(future))
    await future
    coro_result = future.result()
    print(coro_result)


aio.run(main())

In main() I create an empty aio.Future object, then I create a task with aio.create_task(coro(future)) using coroutine which takes aio.Future object. Then I 'run' the empty future with await future. Somehow this line runs the task instead of running the empty coroutine! I don't understand how it works and why it goes like this, because I expect the line await future to run the empty future, not task!

If I reorganize my main() like this:

import asyncio as aio


async def coro(future: aio.Future):
    print('Coro start')
    await aio.sleep(3)
    print('Coro finish')
    future.set_result('coro result')


async def main():
    future = aio.Future()
    await aio.create_task(coro(future))
    # await future
    coro_result = future.result()
    print(coro_result)


aio.run(main())

I get the same result but the code behaviour becomes much more explicit for me.

I have a code:

import asyncio as aio


async def coro(future: aio.Future):
    print('Coro start')
    await aio.sleep(3)
    print('Coro finish')
    future.set_result('coro result')


async def main():
    future = aio.Future()
    aio.create_task(coro(future))
    await future
    coro_result = future.result()
    print(coro_result)


aio.run(main())

In main() I create an empty aio.Future object, then I create a task with aio.create_task(coro(future)) using coroutine which takes aio.Future object. Then I 'run' the empty future with await future. Somehow this line runs the task instead of running the empty coroutine! I don't understand how it works and why it goes like this, because I expect the line await future to run the empty future, not task!

If I reorganize my main() like this:

import asyncio as aio


async def coro(future: aio.Future):
    print('Coro start')
    await aio.sleep(3)
    print('Coro finish')
    future.set_result('coro result')


async def main():
    future = aio.Future()
    await aio.create_task(coro(future))
    # await future
    coro_result = future.result()
    print(coro_result)


aio.run(main())

I get the same result but the code behaviour becomes much more explicit for me.

Share Improve this question asked Jan 20 at 8:05 IzaeDAIzaeDA 3335 bronze badges 2
  • 2 It makes more sense if you think of await as meaning "wait for", like it does in the dictionary, instead of meaning "run". – Dan Getz Commented Jan 20 at 16:27
  • 1 await aio.create_task(coro(future)) waits for corutine to finish, await future waits for future to get assigned final result. You need to have at the least one await to avoid error – Serge Commented Jan 20 at 19:30
Add a comment  | 

3 Answers 3

Reset to default 3

First, let's clear up some terminology. You said, "Then I 'run' the empty future with await future ..." A future is not "run". A future represents a value that will be set in the future. If you await the future, there has to be some other task that calls set_result on the future before your await is satisfied.

Then you said, "Somehow this line (await future) runs the task instead of running the empty coroutine!" I don't know what you mean by an "empty coroutine". Let's see what is actually happening:

In main you create a task with aio.create_task(coro(future)). First, you should ideally assign the task instance that was created to some variable so that a reference to the task exists preventing the task from being prematurely garbage collected (and thus terminated). For example,

task = aio.create_task(coro(future))

Now that you have created a task, it will potentially execute (depending on what other tasks exist) as soon as main either executes an await statement or returns. Thus the mere fact that you execute await future is sufficient to cause function coro to start running. coro sets a result in the future and when it issues an await or returns, then another task gets a chance to run. In this case coro returns and the await issued on the future by main completes.

Your second example is less than ideal. main wants to wait for the future to be set with a value. This setting is being done by coro so clearly if you wait for coro to complete you will discover that your future has been set. But what if coro is a very long running task and sets a value in the future long before it terminates? In this case main will be waiting an unnecessarily long period of time since the future it is interested in was set long before coro ever terminated. Your code should therefore be:

import asyncio as aio

async def coro(future: aio.Future):
    print('Coro start')
    # For demo purposes we set the future right away:
    future.set_result('coro result')
    await aio.sleep(3)
    print('Coro finish')


async def main():
    future = aio.Future()
    task = aio.create_task(coro(future))
    # We are interested in examining the future as soon
    # as it gets a result, which may be before coro terminates:
    await future
    # Now we can call `result` on the future even though coro will
    # not terminate for 3 more seconds:
    coro_result = future.result()
    print(coro_result)
    await task  # Give coro a chance to finish

aio.run(main())

Prints:

Coro start
coro result
Coro finish

I feel like there is a misconception of what a future is and how it works, so simply a future is an object that has a result which is not available yet and will be set in the future (obvious by the name), and it is mainly used inside of Asnycio through other functions and such but we can use futures outside of it as well.

Its pretty much a placeholder which we can set either a result/value to or an exception, and in the situation where you are creating a task and it "fills" up the future that's the point of it, so that the future is populated.

The following will explain the concept better in a code manner:

async def main():

    future = aio.Future()

    # Create a task that will set the result of 'future'
    aio.create_task(coro(future))

    # This will "block" until 'future' gets a result or exception
    await future

    # Retrieves the result of 'future' once it's set
    coro_result = future.result()

    print(coro_result)

Hope this helps :)

To keep it simple, aio.create_task(coro(future)) already schedules (launches) the coro coroutine . Yet main is not suspended right away, it continues to execute (unless one or another await gives task a chance to execute). await future suspends the main coroutine and waits until future gets assigned the final result. It does not start the task per se, the task is already launched, just gives its opportunity to run and ensures completion.

await aio.create_task(coro(future)) in the main co-routine not only creates the task, but awaits until the coro task is finished.

Either way you need at least one await to make sure the result is ready

发布评论

评论列表(0)

  1. 暂无评论