Thomas Johnson
Thomas Johnson

Reputation: 11658

Removing async pollution from Python

How do I remove the async-everywhere insanity in a program like this?

import asyncio


async def async_coro():
    await asyncio.sleep(1)


async def sync_func_1():
    # This is blocking and synchronous
    await async_coro()


async def sync_func_2():
    # This is blocking and synchronous
    await sync_func_1()


if __name__ == "__main__":
    # Async pollution goes all the way to __main__
    asyncio.run(sync_func_2())

I need to have 3 async markers and asyncio.run at the top level just to call one async function. I assume I'm doing something wrong - how can I clean up this code to make it use async less?

FWIW, I'm interested mostly because I'm writing an API using asyncio and I don't want my users to have to think too much about whether their functions need to be def or async def depending on whether they're using a async part of the API or not.

Upvotes: 1

Views: 1824

Answers (2)

user4815162342
user4815162342

Reputation: 154911

If you must do that, I would recommend an approach like this:

import asyncio, threading

async def async_coro():
    await asyncio.sleep(1)

_loop = asyncio.new_event_loop()
threading.Thread(target=_loop.run_forever, daemon=True).start()

def sync_func_1():
    # This is blocking and synchronous
    return asyncio.run_coroutine_threadsafe(async_coro(), _loop).result()

def sync_func_2():
    # This is blocking and synchronous
    sync_func_1()

if __name__ == "__main__":
    sync_func_2()

The advantage of this approach compared to one where sync functions run the event loop is that it supports nesting of sync functions. It also only runs a single event loop, so that if the underlying library wants to set up e.g. a background task for monitoring or such, it will work continuously rather than being spawned each time anew.

Upvotes: 0

Thomas Johnson
Thomas Johnson

Reputation: 11658

After some research, one answer is to manually manage the event loop:

import asyncio


async def async_coro():
    await asyncio.sleep(1)


def sync_func_1():
    # This is blocking and synchronous
    loop = asyncio.get_event_loop()
    coro = async_coro()
    loop.run_until_complete(coro)


def sync_func_2():
    # This is blocking and synchronous
    sync_func_1()


if __name__ == "__main__":
    # No more async pollution
    sync_func_2()

Upvotes: 1

Related Questions