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.