2

On FastAPI, I have an endpoint that calls get_1 or get_2 coroutine function below.

get_1 uses await redis.get(key)

get_2 uses await asyncio.ensure_future(redis.get(key))

Is there any difference between the 2 functions in terms of functionality and performance?

#redis.py

import asyncio
import aioredis

async def get_1(key):
   redis = aioredis.from_url("redis://localhost")
   value = await redis.get(key)
   return value

async def get_2(key):
   redis = aioredis.from_url("redis://localhost")
   value = await asyncio.ensure_future(redis.get(key))
   return value
Rocherlee
  • 2,536
  • 1
  • 20
  • 27
  • I suspect that `get_2()` is much older code. The async framework has become cleaner with each release of Python. `asyncio.ensure_future` is a low-level API and should probably shouldn't need to use it in most code anymore. – Frank Yellin Nov 13 '21 at 22:08
  • I agree with newer Python, `get_2()` should use `asyncio.create_task`. Then I still have the same question. – Rocherlee Nov 13 '21 at 22:14

1 Answers1

2

First of all, to understand what exactly await does and how task differs from future, I recommend starting with this topic and, of course, official documentation.

As for your question, at first glance, the both expressions await coro() and await create_task(coro()) do the same thing. They start coroutine, wait for it to complete and return the result.

But there are a number of important difference:

  • The await coro() leads to direct call to the coroutine code without returning execution path to event loop. This issue was explained in this topic.
  • The await create_task(coro()) leads to wrapping the coroutine in a task, scheduling its execution in the event loop, returning execution path to event loop and then waiting for the result. In this case, before executing of the target coroutine(scheduled as a task) other already sheduled tasks can be executed. Usually, await is not used with create_task, to allow a spawned task to run in parallel, but sometimes it is needed, the example in the next paragraph
  • The await coro() executes the target coroutine within the current context of variables, and the await create_task(coro()) within the copy of the current context (more details in this topic).

Based on the above, most likely you want await coro(), leaving the second expression for more specific cases.

alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • About the 2nd point: I think `await create_task(coro())` creates a task and **immediately** wait for it, thus **should block the execution** the same way that the 1st point does. Your explanation means `other already scheduled tasks can be executed` under `await create_task(coro())`. I wonder if there is any reference to support this 2nd point? – Rocherlee Nov 15 '21 at 09:50
  • Of course, excerpt from the official [documentation](https://docs.python.org/3/library/asyncio-task.html#asyncio.Task): *[Task is] a future-like object. [...] Event loops use cooperative scheduling: an event loop runs one Task at a time. While a Task awaits for the completion of a Future, the event loop **runs other Tasks**, callbacks, or performs IO operations.* – alex_noname Nov 15 '21 at 10:13
  • I'd like to have an argument. The excerpt explains when a `coro()` of a Task is waiting for something, the event loop takes back control and can run another `coro()` of another Task. It doesn't explain what happen at `await create_task(coro())`, i.e, when `create_task` (1) and `await` (2) occur together. Your 2nd point says in a _very short period of time_ between (1) and (2), the event loop **can** take back control to run something else. My suspicion is that the event loop **can't** take back control between (1) and (2), because maybe the _very short period of time_ is like 0? – Rocherlee Nov 15 '21 at 14:56
  • The key point is that the event loop does not take control by itself, tasks give it over when they see fit. In particular, `await task` will certainly transfer control to the event loop, there is no place for time races here. – alex_noname Nov 15 '21 at 16:37