ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

There are two modes that future callbacks are called. Either they're called directly by the future, or they're scheduled with an executor. It's up to the executor to decide what order to call them in.

This example creates a future without an executor.

from functools import partial
from rclpy.task import Future

def callback(name, future):
    print(f"I am callback {name}")

f = Future()
f.add_done_callback(partial(callback, 'alice'))
f.add_done_callback(partial(callback, 'bob'))
f.add_done_callback(partial(callback, 'charlie'))
f.set_result("foobar")

It calls done callbacks in the order they're added

I am callback alice
I am callback bob
I am callback charlie

This future schedules callbacks with a SingleThreadedExecutor.

import rclpy
from rclpy.executors import SingleThreadedExecutor

rclpy.init()
s = SingleThreadedExecutor()
f = Future(executor=s)

f.add_done_callback(partial(callback, 'amy'))
f.add_done_callback(partial(callback, 'brice'))
f.add_done_callback(partial(callback, 'cole'))
f.set_result("foobar")

s.spin_once()
s.spin_once()
s.spin_once()
s.spin_once()

rclpy.shutdown()

It happens to call them in reverse.

I am callback cole
I am callback brice
I am callback amy

They're reversed because the executor checks which tasks are ready in reverse. Reversing the order means if an old task has yield'd or await'd because it's waiting for something to happen, then a new task (appended to the end of the task list) gets to run first and potentially unblock the old task.

That doesn't mean an executor is guaranteed to execute the tasks in reverse though. The order of callbacks in a Multithreaded executor depends on the order the operating system schedules threads.

import rclpy
from rclpy.executors import MultiThreadedExecutor

rclpy.init()
s = MultiThreadedExecutor()
f = Future(executor=s)

for i in range(100):
    f.add_done_callback(partial(callback, i))
    f.set_result("foobar")

for i in range(100):
    s.spin_once()

rclpy.shutdown()

It tends towards reverse order but is still pretty random

...
I am callback 95
I am callback 93
I am callback 92
I am callback 94
I am callback 89
I am callback 88
I am callback 90
...