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 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.
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'))
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.
In the example above, move commands are issued as pending Tasks.
The program then waits until both Tasks complete.
In the example above, the second move command is issued without awaiting the first command promise. The program awaits both promises together.
In the example above, move commands are issued as CompletableFutures.
The program then waits until both CompletableFutures complete.
In example 1, move commands are issued as pending Tasks in a ThrowingTaskGroup. In example 2, we parallelize execution using async let. In either case, the program will wait for all async tasks to complete.
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.
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.