What's the difference between loop.create_task, asyncio.async/ensure_future and Task?

Question:

I’m a little bit confused by some asyncio functions. I see there is BaseEventLoop.create_task(coro) function to schedule a co-routine. The documentation for create_task says its a new function and for compatibility we should use asyncio.async(coro) which by referring to docs again I see is an alias for asyncio.ensure_future(coro) which again schedules the execution of a co-routine.

Meanwhile, I’ve been using Task(coro) for scheduling co-routine execution and that too seems to be working fine. so, what’s the difference between all these?

Asked By: Elektito

||

Answers:

As you’ve noticed, they all do the same thing.

asyncio.async had to be replaced with asyncio.ensure_future because in Python >= 3.5, async has been made a keyword[1].

create_task‘s raison d’etre[2]:

Third-party event loops can use their own subclass of Task for interoperability. In this case, the result type is a subclass of Task.

And this also means you should not create a Task directly, because different event loops might have different ways of creating a “Task”.

Edit

Another important difference is that in addition to accepting coroutines, ensure_future also accepts any awaitable object; create_task on the other hand just accepts coroutines.

Answered By: Jashandeep Sohi

Future

A Future is an object that is supposed to have a result in the future.

Or more officially:

A Future is an awaitable object that represents an eventual result of an asynchronous operation.

Object is awaitable if it can be used in an await expression.

So, any Future can be await‘ed.

Task

Docs:

Tasks are used to schedule coroutines concurrently.

When a coroutine is wrapped into a Task with functions like
asyncio.create_task() the coroutine is automatically scheduled to run
soon.

A Task actually is a Future:

A Future-like object that runs a Python coroutine.

Task is subclass of a Future.

asyncio.Taskinherits from Future all of its APIs except
Future.set_result() and Future.set_exception().

create_task(coro)

Docs:

Wraps the coro coroutine into a Task and schedules its execution.
Returns the Task object.

asyncio.ensure_future(obj, …)

It actually ensures that obj is a Future. If it’s not it creates a Task from obj. That’s it.

Some conclusions

  • If obj already is a Future, ensure_future will return the same obj. In this case return value of a function may be a Future and not a Task – since Task is just a subclass of a Future.
  • With ensure_future you CAN do this:
# will create a Task
task = asyncio.ensure_future(coroutine())
# will ensure that it is a Task
task = asyncio.ensure_future(task)

or this:

# will create a Task
task = asyncio.create_task(coroutine())
# will ensure that it is a Task
task = asyncio.ensure_future(task)

So, you can call ensure_future on a Task.
But you CAN’T do the same with create_task:

# will create a Task
task = asyncio.create_task(coroutine())
# will raise an exception
task = asyncio.create_task(task)

TypeError: a coroutine was expected, got Task

Pay attention

According to docs:

create_task() is the preferred way for creating new Tasks.

Deprecated since version 3.10: Deprecation warning is emitted if obj
is not a Future-like object and loop is not specified and there is no
running event loop.

Answered By: egvo