Asynchronous Operation

Many times, especially when working in a client-server model, you may want to perform operations “in the background”, i.e., send a batch of work to the server and continue with your local operation. At some later point, you may want to poll for the completion of the work, or perhaps be notified of its completion using a callback function.

RPyC is very well-suited for asynchronous work. In fact, the protocol itself is asynchronous, and synchronicity is layered on top of that – by issuing an asynchronous request and waiting for its completion. However, since the synchronous modus-operandi is the most common one, the library exposes a synchronous interface, and you’ll need to explicitly enable asynchronous behavior.

async_()

The wrapper async_() takes any callable netref and returns an asynchronous-wrapper around that netref. When invoked, this wrapper object dispatches the request and immediately returns an AsyncResult, instead of waiting for the response.

Usage

Create an async wrapper around the server’s time.sleep function

async_sleep = rpyc.async_(conn.modules.time.sleep)

And invoke it like any other function, but instead of blocking, it will immediately return an AsyncResult

res = async_sleep(5)

Which means your client can continue working normally, while the server performs the request. There are several pitfalls using async_, be sure to read the Notes section!

You can test for completion using res.ready, wait for completion using res.wait(), and get the result using res.value. You may set a timeout for the result using res.set_expiry(), or even register a callback function to be invoked when the result arrives, using res.add_callback().

Notes

The returns async proxies are cached by a weak-reference. Therefore, you must hold a strong reference to the returned proxy. Particularly, this means that instead of doing

res = async_(conn.root.myfunc)(1,2,3)

Use

myfunc_async = async_(conn.root.myfunc)
res = myfunc_async(1,2,3)

Furthermore, async requests provide no guarantee on execution order. In particular, multiple subsequent async requests may be executed in reverse order.

timed()

timed allows you to set a timeout for a synchronous invocation. When a timed function is invoked, you’ll synchronously wait for the result, but no longer than the specified timeout. Should the invocation take longer, a AsyncResultTimeout will be raised.

Under the hood, timed is actually implemented with async_: it begins dispatches the operation, sets a timeout on the AsyncResult, and waits for the response.

Example

# allow this function to take up to 6 seconds
timed_sleep = rpyc.timed(conn.modules.time.sleep, 6)

# wait for 3 seconds -- works
async_res = timed_sleep(3)  # returns immediately
async_res.value             # returns after 3 seconds

# wait for 10 seconds -- fails
async_res = timed_sleep(10) # returns immediately
async_res.value             # raises AsyncResultTimeout

Background Serving Thread

BgServingThread is a helper class that simply starts a background thread to serve incoming requests. Using it is quite simple:

bgsrv = rpyc.BgServingThread(conn)
# ...
# now you can do blocking stuff, while incoming requests are handled in the background
# ...
bgsrv.stop()

Using the BgServingThread allows your code (normally the client-side) to perform blocking calls, while still being able to process incoming request (normally from the server). This allows the server to send “events” (i.e., invoke callbacks on the client side) while the client is busy doing other things.

For a detailed example show-casing the BgServingThread, see Events in the tutorial.