The asyncio docs make it clear that asyncio code should not call blocking code directly, also specifying the way to run blocking code with async code:
Blocking (CPU-bound) code should not be called directly. For example, if a function performs a CPU-intensive calculation for 1 second, all concurrent asyncio Tasks and IO operations would be delayed by 1 second.
An executor can be used to run a task in a different thread or even in a different process to avoid blocking the OS thread with the event loop.
However, this description is not very specific about at what point an executor should be used. It's clear that "a CPU-intensive calculation for 1 second" would be a problem, but would 0.1s be a problem? or 0.01s?
The docs also provide the example
def cpu_bound():
return sum(i * i for i in range(10 ** 7))
as something to run in an executor (which runs in less than a second).
(Though they are likely using this as an example of using threads vs processes, it is still an example of what I mean -- would I run it in an executor if it was range(10 ** 6)
, etc.)
In this answer, it is stated that
The majority of the standard library consists of regular, 'blocking' function and class definitions. They do their work quickly, so even though they 'block', they return in reasonable time.
...
Loads of standard library functions and methods are fast, why would you want to run
str.splitlines()
orurllib.parse.quote()
in a separate thread when it would be much quicker to just execute the code and be done with it?
But what counts as "reasonable time"? When can I "just execute the code and be done with it"?
My questions are:
- How do you determine that an executor is needed?
- What's actually happening if your code is "blocking" too long? What are the signs that this is the case?