Zaber Launcher Tutorials
Zaber Motion Library
Sample Projects
Virtual DeviceDropdown icon
About3D Viewer
AccountDropdown icon
Sign InSign Up
Zaber Motion LibraryGetting Started
How-to Guides
Communication
Controlling multiple devicesFinding the right serial port nameImproving performance of X-USBDC (FTDI)Interaction with Zaber LauncherTCP/IP (network) communication
Library Features
Arbitrary unit conversionsError handlingEventsG-CodeNon-blocking (simultaneous) axis movementSaving and loading stateSending arbitrary commands
Device Features
Device I/OLockstepOscilloscopePosition Velocity Time (PVT)PVT Sequence GenerationSettingsStreamed movementTriggersWarning flags
Product-Specific APIs
MicroscopeProcess controllerVirtual devices
Advanced
Building a Standalone Application with MATLAB CompilerBuilding the C++ Library from Source CodeCustom transportsDevice databaseLoggingMATLAB Migration GuidePackaging Your Program with PyinstallerThread safety
API ReferenceSupportBinary Protocol (Legacy)
© 2026 Zaber Technologies Inc.

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.

Python
C#
C++
JavaScript
MATLAB
Java
Swift
MATLAB (legacy)
# 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()
var axis1 = device.GetAxis(1);
var axis2 = device.GetAxis(2);

axis1.MoveAbsolute(3, Units.Length_Centimetres, waitUntilIdle: false);
axis2.MoveAbsolute(3, Units.Length_Centimetres, waitUntilIdle: false);

// additional code

axis1.WaitUntilIdle();
axis2.WaitUntilIdle();
// The JavaScript API does not have synchronous movement methods.
// Consult the last section for an example of asynchronous movement in JavaScript.
// import zaber.motion.ascii.Axis;
// import zaber.motion.ascii.Connection;
// import zaber.motion.ascii.Device;
// import zaber.motion.Units;

Axis axis1 = device.getAxis(1);
Axis axis2 = device.getAxis(2);

axis1.moveAbsolute(3, Units.LENGTH_CENTIMETRES, false);
axis2.moveAbsolute(3, Units.LENGTH_CENTIMETRES, false);

// additional code

axis1.waitUntilIdle();
axis2.waitUntilIdle();
% import zaber.motion.ascii.Connection;
% import zaber.motion.Units;

axis1 = device.getAxis(1);
axis2 = device.getAxis(2);

axis1.moveAbsolute(3, Units.LENGTH_CENTIMETRES, false);
axis2.moveAbsolute(3, Units.LENGTH_CENTIMETRES, false);

% additional code

axis1.waitUntilIdle();
axis2.waitUntilIdle();
% import zaber.motion.ascii.Connection;
% import zaber.motion.Units;

axis1 = device.getAxis(1);
axis2 = device.getAxis(2);

axis1.moveAbsolute(3, Units.Length("cm"), waitUntilIdle=false);
axis2.moveAbsolute(3, Units.Length("cm"), waitUntilIdle=false);

% additional code

axis1.waitUntilIdle();
axis2.waitUntilIdle();
Axis axis1 = device.getAxis(1);
Axis axis2 = device.getAxis(2);

axis1.moveAbsolute(3, Units::LENGTH_CENTIMETRES, false);
axis2.moveAbsolute(3, Units::LENGTH_CENTIMETRES, false);

// additional code

axis1.waitUntilIdle();
axis2.waitUntilIdle();
let axis1 = try device.getAxis(axisNumber: 1)
let axis2 = try device.getAxis(axisNumber: 2)

try await axis1.moveAbsolute(position: 3, unit: Units.Length.cm, waitUntilIdle: false);
try await axis2.moveAbsolute(position: 3, unit: Units.Length.cm, waitUntilIdle: false);

// additional code

try await axis1.waitUntilIdle();
try await axis2.waitUntilIdle();

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.

Python
C#
C++
JavaScript
MATLAB
Java
Swift
MATLAB (legacy)
# 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'))
var group = new AxisGroup(new Axis[] { device.GetAxis(1), device.GetAxis(2) });
group.MoveAbsolute(new Measurement(1, Units.Length_Centimetres), new Measurement(2, Units.Length_Centimetres));
const group = new AxisGroup([device.getAxis(1), device.getAxis(2)]);
await group.moveAbsolute(new Measurement(1, Length.cm), new Measurement(2, Length.cm));
// import zaber.motion.ascii.Axis;
// import zaber.motion.ascii.AxisGroup;
// import zaber.motion.ascii.Connection;
// import zaber.motion.ascii.Device;
// import zaber.motion.Measurement;
// import zaber.motion.Units;

AxisGroup group = new AxisGroup(new Axis[] {device.getAxis(1), device.getAxis(2)});
group.moveAbsolute(new Measurement(1, Units.LENGTH_CENTIMETRES), new Measurement(2, Units.LENGTH_CENTIMETRES));
% import zaber.motion.ascii.AxisGroup;
% import zaber.motion.Measurement;
% import zaber.motion.Units;

group = AxisGroup([device.getAxis(1), device.getAxis(2)]);
group.moveAbsolute(Measurement(1, Units.LENGTH_CENTIMETRES), Measurement(2, Units.LENGTH_CENTIMETRES));
% import zaber.motion.ascii.AxisGroup;
% import zaber.motion.Measurement;
% import zaber.motion.Units;

group = AxisGroup([device.getAxis(1), device.getAxis(2)]);
group.moveAbsolute(Measurement(1, Units.Length("cm")), Measurement(2, Units.Length("cm")));
auto group = AxisGroup({ device.getAxis(1), device.getAxis(2) });
group.moveAbsolute(Measurement(1, Units::LENGTH_CENTIMETRES), Measurement(2, Units::LENGTH_CENTIMETRES));
let group = AxisGroup(axes: [ try device.getAxis(axisNumber: 1), try device.getAxis(axisNumber: 2) ])
try await group.moveAbsolute(
    Measurement(value: 1, unit: Units.Length.cm),
    Measurement(value: 2, unit: Units.Length.cm)
)

Asynchronous Operations

For languages which allow asynchronous operations, an alternative approach may be taken:

Python
C#
C++
JavaScript
MATLAB
Java
Swift
MATLAB (legacy)
# 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)
var taskAxis1 = axis1.MoveAbsoluteAsync(3, Units.Length_Centimetres);
var taskAxis2 = axis2.MoveAbsoluteAsync(3, Units.Length_Centimetres);

Task.WaitAll(taskAxis1, taskAxis2);
// const { ascii: { Connection }, Length } = require('@zaber/motion');

const promiseAxis1 = axis1.moveAbsolute(3, Length.cm);
const promiseAxis2 = axis2.moveAbsolute(3, Length.cm);

await Promise.all([promiseAxis1, promiseAxis2]);
// import java.util.concurrent.CompletableFuture;
// import java.util.concurrent.ExecutionException;
// import zaber.motion.ascii.Axis;
// import zaber.motion.ascii.Connection;
// import zaber.motion.ascii.Device;
// import zaber.motion.Units;

// add the following to the main() function:
CompletableFuture<Void> futureAxis1 = axis1.moveAbsoluteAsync(3, Units.LENGTH_CENTIMETRES);
CompletableFuture<Void> futureAxis2 = axis2.moveAbsoluteAsync(3, Units.LENGTH_CENTIMETRES);

try {
    CompletableFuture.allOf(futureAxis1, futureAxis2).get();
} catch (InterruptedException | ExecutionException e) {
    throw new RuntimeException(e);
}
% No alternative approach can be taken using the current MATLAB library.
% No alternative approach can be taken using the current MATLAB library.
// No alternative approach can be taken using the current C++ library.
// Example 1: Parallel execution with throwing task group
try await withThrowingTaskGroup(of: Void.self) { group in
    let axis1 = try device.getAxis(axisNumber: 1)
    let axis2 = try device.getAxis(axisNumber: 2)

    group.addTask { try await axis1.moveAbsolute(position: 3, unit: Units.Length.cm) }
    group.addTask { try await axis2.moveAbsolute(position: 3, unit: Units.Length.cm) }
}

// Example 2: Parallel async calls with async let
async let result1: () = axis1.moveAbsolute(position: 3, unit: Units.Length.cm)
async let result2: () = axis2.moveAbsolute(position: 3, unit: Units.Length.cm)

_ = try await [result1, result2]

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.

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.