32

This creates a Future:

loop.run_in_executor(None, some_fn) 

This creates a Task:

asyncio.create_task(some_fn()) 

What is the difference between the two?

According to my understanding:

  • The former creates a new thread and executes the function (which is typically IO-bound) concurrently.

  • The latter creates a task within the same thread and executes the function concurrently.

Is this correct? The documentation doesn't clearly explain the differences between the two.

2 Answers 2

37

What is the difference between [futures and tasks]?

In short, future is the more general concept of a container of an async result, akin to a JavaScript promise. Task is a subclass of future specialized for executing coroutines.

Nothing in the definition of asyncio future indicates multi-threaded execution, and asyncio is in fact strongly single-threaded. It's a specific feature of run_in_executor that it safely synchronizes functions it invokes in other threads with an asyncio future it creates and returns.

Also, be careful not to confuse asyncio futures with concurrent.futures futures, the latter being indeed multi-threaded. Despite similarities in the API coming from the fact that asyncio futures were inspired by the ones from concurrent.futures, they work in conceptually different ways and cannot be used interchangeably.

Sign up to request clarification or add additional context in comments.

2 Comments

what are other subclasses of futures except Tasks? it other word, what other job can be done by a future that is not ''executing of coroutines"?
@garej An ordinary future is useful to connect callbacks, like JS promises. In particular it serves as bridge between callbacks and coroutines. You can create a future and pass it to a callback which will later invoke fut.set_result(x). A coroutine can await it in the meantime (or await several such futures with gather() etc.) When the callback completes the future, the coroutine that awaited it gets resumed and await <future> gets evaluated to the x the callback passed to set_result(). Asyncio plumbing code, the transports and protocols layer, is all written in this style.
13

*My answers are assuming you use the default loops provided by Python based on your OS with CPython.

The asyncio.create_task method is used to schedule the execution of a coroutine (here some_fn) on the event loop. Since the event loop runs on a single thread, the coroutine will be executed asynchronously to the current context by using cooperative multitasking.

Contrary to that, loop.run_in_executor(None, some_fn), will use the ThreadPoolExecutor to create a thread pool, and perform the tasks, usually BlockingIO, with preemptive multitasking. You can see here the implementation for ThreadPoolExecutor in CPython. In the implementation, you'll see that it creates a queue and a pool of threads which will get tasks from the queue and perform them. So yes - it will create a new thread for your scheduled task.


A couple of notes:

  1. A Task is a Future
  2. Generally speaking, if your code does not have blockingIO methods, and you can leverage asyncio for all your IOs, you will be better just using the event loop, without the need to use an Executor explicitly. This will be both more efficient since your code now uses cooperative multitasking, and will simplify execution since you know when your code gives back control to the event loop, thus managing concurrency (e.g. race condition) is either very simple or even non-existent.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.