pytweezer.parallel module¶
Run several blocking device calls concurrently, without async/await.
Some experiment steps need blocking device calls issued at the same time rather
than back-to-back. The canonical case: arm a camera, start one MotMaster in
trigger mode (its Go() blocks, waiting on a hardware trigger), and start a
second MotMaster whose sequence emits that trigger. The armed MotMaster’s call
will not return until the second one fires, so the two must be launched
concurrently:
from pytweezer.parallel import run_parallel, after
from pytweezer.servers.device_client import get_device
cam = get_device("Rb HamCam")
mm1 = get_device("Rb MotMaster Server")
mm2 = get_device("CaF MotMaster Server")
cam.start_acquisition() # arm the camera (returns immediately)
mm1.set_trigger_mode(True) # mm1's Go() will wait for a hardware trigger
frame, _, _ = run_parallel(
lambda: cam.acquire_n_frames(1), # blocks reading the frame
mm1.start_motmaster_experiment, # armed; waits for trigger
after(0.05, mm2.start_motmaster_experiment), # fires 50 ms later
)
Each call runs in its own thread. Every device is a separate server process, so
a get_device() call is just a blocking
socket round-trip whose recv releases the GIL — the threads overlap for real,
and this needs no AsyncioClient/asyncio.gather and works from the PyQt5
GUI (which has no qasync). get_device_async()
+ asyncio.gather remains available as the lower-level async alternative.
Each parallel call must use a different client. A single sipyco Client is
not safe to share across threads; give each concurrent call its own device
handle (the camera + two-MotMaster pattern above already does).
- pytweezer.parallel.after(delay, call)[source]¶
Wrap
callso it waitsdelayseconds (inside its own thread) first.Use this to stagger a call within
run_parallel()— e.g. hold the triggering MotMaster briefly so the armed one is waiting before the trigger fires. The sleep happens on the call’s own thread, so it does not delay the other parallel calls. Returns a zero-argument callable that forwardscall’s return value.
- pytweezer.parallel.run_parallel(*calls, timeout=None)[source]¶
Run zero-argument callables concurrently, one thread each; return results in order.
- Parameters:
*calls – Callables taking no arguments. Supply arguments with a
lambdaorfunctools.partial(), and useafter()to stagger a call’s start.timeout – Optional overall deadline in seconds for all calls to finish. On expiry a
TimeoutErroris raised; the still-running threads are daemon threads and are abandoned (Python threads cannot be force-killed).None(default) waits indefinitely. Each client’s own socket timeout still applies independently.
- Returns:
Results in the same order as
calls(regardless of finish order).- Return type:
- Raises:
The call's own exception if exactly one call fails; an –
waited on before raising. –