How to use blocking functions with asyncio

2019-04-01 12:00发布

问题:

I'm using django ORM in a project(outside of django). My workflow is

  1. selecting objects by the django ORM and
  2. then send it to a message queue using an asyncio lib

The problem is you can't call blocking functions in async environment and you can not use async/await in blocking environment.

I have come up with 2 solutions:

  1. The whole program should be async. And use loop.run_in_executor to call blocking functions when needed.

  2. The whole program should be sync. And use asyncio.run()(Python 3.7) to call async functions needed.

I can't decide which one is better approach.

I know a similar question has been asked before. My question is is there a general rule when trying to combine blocking and non-blocking code?

回答1:

Given the choice between those two, I would definitely recommend approach #1.

#2 has the downside that you're missing out on a lot of asyncio functionality by splitting up asyncio calls into separate little event loop runs. For example, you can't create a "background" task whose execution spans several calls to asyncio.run(), and that kind of thing can be very useful for logging, monitoring, or timeout. (Using asyncio.run could also be a performance issue because it creates a whole new event loop on every invocation, but this can be fixed by switching to run_until_complete.)

But there is also a third option:

  • Create a separate thread that only executes loop.run_forever() and waits to be given work to do. The remainder of the program consists of normal blocking code that can request something from asyncio using asyncio.run_coroutine_threadsafe(). That function doesn't block; it immediately returns a concurrent.futures.Future which you can pass around and whose result() method automatically waits for the result to be available. It supports additional features, such as waiting for multiple instances to complete in parallel using wait, the as_completed iterator, etc.

This approach IMHO combines the best characteristics of the two options from the question. It leaves blocking code truly blocking, still being allowed to wait for things to happen, spawn threads etc., without forcing the use of async def and run_in_executor across the board. At the same time the asyncio parts can be written using asyncio best practices, with a long-running event loop servicing the whole program. You just need to be careful for all interfacing with the event loop from the rest of the application (even to call something as simple as loop.stop) to be done using loop.call_soon_threadsafe and asyncio.run_coroutine_threadsafe.