Non-blocking (simultaneous) axis movement
Movement methods in the library are by default blocking, meaning the method calls return (end) when the issued movement is finished by the device. As a result, by default they only move one axis at a time. In some cases moving multiple axes simultaneously is desirable. For example, simultaneously moving two axes may shorten the movement time.
One option is to use language concurrency mechanisms, such as threads, to manage simultaneous movements. The library also includes two options for simultaneous movement without threads.
Movement Interleaving
Movement interleaving allows you to issue multiple movement commands to different axes without blocking the code execution by waiting for the movement to finish.
# from zaber_motion.ascii import Connection
# from zaber_motion import Units
axis1 = device.get_axis(1)
axis2 = device.get_axis(2)
axis1.move_absolute(3, Units.LENGTH_CENTIMETRES, wait_until_idle=False)
axis2.move_absolute(3, Units.LENGTH_CENTIMETRES, wait_until_idle=False)
# additional code
axis1.wait_until_idle()
axis2.wait_until_idle()
In the example above, movement commands are issued to two axes of one device.
An optional third
wait_until_idle
argument of the
move_absolute
method is set to false.
This argument value causes the method to return immediately after issuing the move command to the device.
As a result, neither movement is blocking so both axes move simultaneously.
You must eventually wait for both axes to finish moving by calling
wait_until_idle
method for each of the axes.
Calling the
wait_until_idle
method also gives the library opportunity to deliver an error in case of the movement failure.
Forgetting to call the
wait_until_idle
method may result in the movement silently failing or being interrupted by the following movements.
Axis Group
If your goal is solely to move multiple axes simultaneously without any other code running during the movement, you can use the
AxisGroup
class.
The
AxisGroup
class allows you to move multiple axes (even if they are part of different devices) simultaneously with a single method call. Be aware that resulting trajectory is not consistent between calls due to communication or operating system delays.
For movements with consistent trajectory, use Streams or PVT instead.
# from zaber_motion.ascii import Connection, AxisGroup
# from zaber_motion import Measurement
group = AxisGroup([device.getAxis(1), device.getAxis(2)])
group.move_absolute(Measurement(1, 'cm'), Measurement(2, 'cm'))
Asynchronous Operations
For languages which allow asynchronous operations, an alternative approach may be taken:
# from zaber_motion.ascii import Connection
# from zaber_motion import Units, wait_all
axis1_coroutine = axis1.move_absolute_async(3, Units.LENGTH_CENTIMETRES)
axis2_coroutine = axis2.move_absolute_async(3, Units.LENGTH_CENTIMETRES)
wait_all(axis1_coroutine, axis2_coroutine)
# Alternatively you can await the coroutines if you are in an async context:
# await asyncio.gather(axis1_coroutine, axis2_coroutine)
In the example above, move commands are issued as coroutines.
The wait_all function executes the coroutines on an asyncio event loop
and blocks the current thread until they all finish.
Note that the device can only handle a certain amount of asynchronous parallel requests.
When the device cannot process the issued requests in time or due to lack of space in receive buffers,
the requests start timing out with
RequestTimeoutException
and the device
may report a system error and require a reset.
Asynchronous Calls and Movement Interleaving are Different
In the above movement interleaving example you have seen the moveAbsolute function called with the optional waitUntilIdle argument
set to false, which causes the functions to return before movement finishes.
In the asynchronous example further down the default value of waitUntilIdle = true is used, which causes the
moveAbsolute functions to wait for movement to finish, but they still return immediately because they return
a Promise, Task or Future that can be queried to see if the movement has finished yet, or awaited to block further
code execution until the movement finishes.
What happens if you use both waitUntilIdle = false and the asynchronous functions?
The functions will return their Promise/Task/Future AND the underlying function code will only send the movement
command and not wait for the move to finish. So when you await the returned values, the await will complete
almost immediately but the device will still be moving. You will still have to use the waitUntilIdle methods
when you want to be sure movement has finished.
This isn't a very useful combination in most cases; in multithreaded languages it allows you to do more work while the move commands are being sent to the device, but that's extra complexity for a tiny time saving compared to the simpler overlapping move case from the first example.