I have few blocking functions foo
, bar
and I can't change those (Some internal library I don't control. Talks to one or more network services). How do I use it as async?. E.g. I wan't to do the following.
results = []
for inp in inps:
val = foo(inp)
result = bar(val)
results.append(result)
This will be inefficient as I can call foo
for the second input while I am waiting for the first and same for bar
. How do I wrap them such that they are usable with asyncio (i.e new async
, await
syntax)?
Lets assume the functions are re-entrant. i.e it is fine to call foo
again when already a previous foo
is processing.
Update
Extending answer with reusable decorator. Click here for example.
def run_in_executor(f):
@functools.wraps(f)
def inner(*args, **kwargs):
loop = asyncio.get_running_loop()
return loop.run_in_executor(None, functools.partial(f, *args, **kwargs))
return inner
There are (sort of) two questions here: first, how to run blocking code asynchronously, and second, how to run async code in parallel (asyncio is single-threaded, so the GIL still applies, so it isn't truly concurrent, but I digress).
Parallel tasks can be created using asyncio.ensure_future, as documented here.
To run synchronous code, you will need to run the blocking code in an executor. Example:
If you want to schedule these tasks using a for loop (as in your example), you have several different strategies, but the underlying approach is to schedule the tasks using the for loop (or list comprehension, etc), await them with asyncio.wait, and then retrieve the results. Example:
Extending the accepted answer to actually solve the problem in question.
Note: Requires python 3.7+
Click here for up to date version of this example and to send pull requests.