Skip to content

Parallel requests

Protocol v2 (the default) tags every request with a request_id, so replies are matched by id instead of by arrival order. One connection can therefore have many requests in flight at once. This page shows the two ways to use that.

When it helps

send_request() blocks the calling thread until the reply arrives. If you have twenty independent reads to do, that is twenty round-trips back to back. With v2 you can send all twenty, then collect the replies as they land.

It does not help a single dependent chain (each request needs the previous reply) -- there is nothing to overlap there.

asyncio -- arequest()

arequest() is a coroutine. Fire several with asyncio.gather():

import asyncio

from aqpxlib import AqProtocolExerciser


async def read_all(ex: AqProtocolExerciser, messages):
    return await asyncio.gather(*(ex.arequest(m) for m in messages))


with AqProtocolExerciser(host="localhost") as ex:   # v2 is the default
    replies = asyncio.run(read_all(ex, my_messages))

Each arequest() returns the decoded PxProtobufMsg, or raises PxAPIError if the server answered with a non-zero status. Pass timeout=<seconds> to bound the wait:

reply = await ex.arequest(msg, timeout=2.0)   # raises TimeoutError past 2 s

On timeout (or if you cancel the task) the in-flight request is abandoned on a best-effort basis: a reply that arrives later is discarded. The connection stays usable.

Threads -- async_request()

If you are not on an event loop, async_request() returns a plain concurrent.futures.Future:

futures = [ex.async_request(m) for m in my_messages]
replies = [f.result(timeout=5) for f in futures]

Events fire on their own thread

The event monitor runs on a background thread, so a handler registered with add_event_handler() does not run on your event loop. To touch the loop from a handler, hop back onto it:

from aqpxlib.event import PxGpioTriggerEvent

loop = asyncio.get_running_loop()


def on_event(evt):
    loop.call_soon_threadsafe(queue.put_nowait, evt)


ex.add_event_handler(PxGpioTriggerEvent, on_event)

v1

Passing protocol_version="v1" selects the legacy single-frame path. It has no arequest() / async_request() -- those raise BackendError -- and one request is in flight at a time.