简体   繁体   中英

Running trivial async code from sync in Python

I'm writing some parser code to be used with async IO functions (using Trio). The parser gets passed an object that exports an async read() method, and calls that method in the course of parsing.

Ordinarily, this code will be run using data straight off the.network, and use Trio's.network functions. For this purpose, Trio is obviously required. However, I'd also like to be able to call the parser with a full message already in hand. In this case, the.network code can be effectively replaced by a trivial async reimplementation of BytesIO or similar.

Because it await s the implementation's async functions, the parser code must therefore also be async. Is there an easy way to run this async code from a sync function, without running a full event loop, in the case where the read() method is guaranteed never to block?

Eg

async def parse(reader):
    d = await reader.read(2)
    # parse data
    d2 = await reader.read(4)
    # more parsing
    return parsed_obj

Can you create an object with a never-blocking async read() method, then easily call parse() from sync code, without using an event loop?

Sure you can.

>>> async def x():
...     return 42
... 
>>> v=x()
>>> v.send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: 42
>>> 

Thus,

>>> def sync_call(p, *a, **kw):
...     try:
...             p(*a, **kw).send(None)
...     except StopIteration as exc:
...             return exc.value
...     raise RuntimeError("Async function yielded")
... 
>>> sync_call(x)
42
>>> 

This does work across function calls, assuming that nothing in your call chain yields to a (non-existing) main loop:

>>> async def y(f):
...     return (await f())/2
... 
>>> sync_call(y,x)
21.0

You mention there's a concept of a "whole message" but then I'd expect there's really two protocols on the wire: a framing protocol (eg size prefix or terminator character) and the message protocol. Something like this:

async def read_and_decode_message(reader, buffer):
    assert isinstance(buffer, bytearray)
    while True:
        end_of_message = find_message_end_in_buffer(buffer)
        if end_of_message is not None:
            break
        buffer.extend(await reader.read_some())
    message_encoded = buffer[:end_of_message]
    del buffer[:end_of_message]
    return decode_message(message_encoded)

If that's the case, surely you could just call decode_message() directly, which should be a synchronous function anyway?

I'm guessing your code isn't anything like this or you would surely have thought of that already, but maybe it could be refactored this way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM