Skip to main content

Executing asynchronous coroutine (asyncio) synchronously in Python

I worked a lot with async functions in languages other than Python. It's usually very straight-forward and easy to find any answers in internet. It was a surprise that solving a very simple question regarding async in Python took me a day. How to execute an async coroutine in a classic synchronous function?

Async library author's position and CPython's GIL made execution of async functions synchronously harder than required. Instead of an exact answer dozens of answers on StackOverflow explain why it is not possible or a bad idea (and a handful of not working suggestions). Ok-ok, I just need a solution!

I had to understand how asyncio works to solve this. So, this code allows to synchronously wait for execution completion of async coroutine without a hot spin-lock:

def sync_exec(coroutine: Awaitable) -> Any:
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        return loop.run_until_complete(coroutine)

    result = []
    thread = Thread(target = _execute_in_thread, args = (coroutine, result))
    thread.start()
    thread.join()
    if isinstance(result[0], BaseException):
        raise result[0]
    return result[0]

def _execute_in_thread(coroutine: Awaitable, result: list):
    try:
        loop = asyncio.new_event_loop()
        result.append(loop.run_until_complete(coroutine))
    except BaseException as exc:
        result.append(exc)

Usage:

result1 = sync_exec(_async_func(0.4))

Tests:

async def _async_func(delay):
    await asyncio.sleep(delay)
    await asyncio.sleep(delay)
    return delay

def test_sync_exec():
    delay1 = 0.4
    delay2 = 0.6
   
    result1 = sync_exec(_async_func(delay1))
    assert result1 == delay1
    result2 = sync_exec(_async_func(delay2))
    assert result2 == delay2

@pytest.mark.asyncio
async def test_sync_exec_from_async():
    test_sync_exec()

Explanation:

The idea is to run the async coroutine (or any awaitable) in another thread with its own async loop. It is needed only in case if there is already a running loop in the current thread. Otherwise we could safely(?) execute it in the current thread.

So, first try-except tests exactly this: is there a running loop in the current thread? If get_running_loop() throws it means there is no loop and we are free to create one and execute a function there in a simple manner. Otherwise we spawn a thread with its own loop and execute there. The result (including an exception) is passed from the thread via a mutable container, a dictionary.

Tests include both scenarios: calling from sync function w/o a loop in the thread; and calling from async function with a loop.


Comments

Popular posts from this blog

Automated currency conversion in text

I have created a js script to automatically convert currency on page according to the current rate. Lets say I write "$5" and want to show its value in other currencies. I just need to enclose it in a tag with class = "exchange" and voila: $5 . To enable this behavior you need to link jQuery (add <script src="https://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript"></script> to the head of page) and the script itself (add <script src="https://drclnatj7kvk6.cloudfront.net/currency.js" type="text/javascript"></script> to the bottom of the page). Script is based on http://fixer.io/ and http://openexchangerates.github.io/money.js/ . Fiddle is available here:  https://jsfiddle.net/40rr05fb/42/embedded/result/ . Language and displaying currencies can be easily set. Extending the script is also straightforward.

Choose free online software project tools

I spent a lot of time to choose high-quality online software project tools for free . Some of them are not so "free" other ones have a lot of ads or have low quality. Trying different solutions I chose following: Instant public chat : there is an options. You may create IRC channel on efnet.org or maintain public chat with Skype (BTW Skype supports up to 150 chat members now). Collaborative documents authoring : Google Docs have no competitors in this area. FAQ service : personally I prefer bravenet 's one. Mail list / discussion group : again Google Groups is the best one. Project management : unfortunately I was unable to find any wholly satisfactory project management online service. If you know one please let me know.

Solving MSB6003 Error for ASP.NET Core Docker Image Build

Trying to build a Linux Docker image for ASP.NET Core project with Typescript support leads to a build error "MSB6003: The specified task executable "node" could not be run". The reason is Linux. On Windows, Microsoft.TypeScript.MSBuild includes tsc.exe. On non-Windows platforms, such as in a Docker container, Microsoft.TypeScript.MSBuild does not include ts.exe and instead shells out to a Node for the TypeScript compiler. The official dotnet/sdk Docker images I think included Node at one point in the past, but they no longer include Node. You will either need to make or find a Docker image with both the dotnet-sdk and Node, or configure some multi-stage build involving the official Node image [ 1 ]. Ok, if I know the reason then I will solve it in a minute. I thought. It turned out to be a quest to install the actual version of Node. Official Microsoft's image for dotnet is based on Debian (12 at the moment for .NET 8.0). So, the straight forward way would be u