-
Notifications
You must be signed in to change notification settings - Fork 316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add asyncio action server example #319
base: rolling
Are you sure you want to change the base?
Conversation
Signed-off-by: tupiznak <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this helps much to be honest.
This is calling run_until_complete(), which means that we're blocking.
Blocking the rclpy executor is not a good idea for the same reasons why it's not a good idea to block the asyncio executor.
I'm open to merge this if you can provide an example of when this is really useful, but of the top off my head I wouldn't recommend this pattern.
I agree with the spirit of this though, I would like to have an asycio friendly executor but that's not currently available.
My idea for that would be to have a thread always waiting on a waitset, and to have API
like async take_msg(self)
in Subscription
which would:
- Add the Subscription handle to the waitset, with an asyncio.Event associated to it. The future returned is the Event.
- The thread handling the waitset will trigger the event when the subscription is ready, and potentially delete the subscription handle from the waitset if no one else is waiting on it.
Yeah, you are right, this is not the best way to embed one Globally it is a blocking code, but only one thread is blocked when it is receiving a command from a client. Everething else inside this is asynchronous. In my current job I have to write asynchronous code. But due to the impossibility of using |
Sorry, I'm still not convinced of the value of the example. |
Thinking about this a bit more, there are ways to integrate the rclpy executor with the asyncio executor without blocking and without modifying rclpy. e.g. you can run a coroutine from the ros callback with https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe and you will need to be both running the asycio loop and the ros loop (aka spinning) in different threads. |
I tried many different options and that one is the best I could think of. |
Long term, I agree with @ivanpauno. To have RMW events being waited on a background thread and dispatched to an Short term, I'm not against local |
You can do that using https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe.
You can already do that in rclpy though https://github.com/ros2/rclpy/blob/95fc7495efded6463a0be47ce6fbed4d9ce26746/rclpy/test/test_executor.py#L166. The only problem is that not all asyncio coroutines can be called, because there's no asycnio event loop. |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I forgot to submit my review. I added one more comment since I revisited this recently.
self.get_logger().info('Received cancel request') | ||
return CancelResponse.ACCEPT | ||
|
||
@asyncio_loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like @ivanpauno said, this is blocking the rclpy executor, which is not recommended.
goal_handle.publish_feedback(feedback_msg) | ||
|
||
# Sleep for demonstration purposes | ||
await asyncio.sleep(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that this allows using asyncio
primitives, but this example is equivalent to getting rid of the asyncio loop and using time.sleep(1)
in the callback.
executor.add_node(action_server) | ||
|
||
# spin | ||
executor.spin() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ros2/rclpy#962 has an idea where an event loop controls the execution, and spin_once()
with a very small timeout is used to handle callbacks. ros2/rclpy#971 improves that use case. That avoids blocking the executor, but prevents using a MultiThreadedExecutor
.
Added the ability to use the asyncio library inside the callback. It can be convenient.