-
Notifications
You must be signed in to change notification settings - Fork 259
Enable limited use of asyncio with executors #971
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
base: rolling
Are you sure you want to change the base?
Conversation
Signed-off-by: Shane Loretz <[email protected]>
3867151 to
e97fdea
Compare
|
This PR also seems to be a small performance improvement. Using the benchmarks from #973 (1527a9e) and running each test 5 times on each branch there's improvement in every single test. The average improvement is 1.75%.
|
|
@sloretz Do you still see this approach as the best way forward? It would be helpful to know what the best approach is to marry |
|
Hi @sloretz, Are there any updates on this PR? The current rclpy executor feels limited, especially since asyncio has become the standard in Python for concurrency. The inability to cancel tasks and notify coroutines upon cancellation restricts the usability of ROS tasks. At work, we developed a workaround inspired by asyncio, that wraps around the rclpy task. Additionally, we implemented an "asyncio-to-ROS" bridge to manage communication with HTTP and serial devices by running separate event loops in threads and passing tasks between them. Replacing parts of the executor with asyncio could significantly simplify things. For example, a thread running the asyncio event loop could manage tasks while the wait set would operate in another thread and create tasks in the event loop. Additionally, multi-threaded execution could leverage asyncio.run_in_executor. I'm happy to contribute to this PR or any other solution you suggest. |
Resolves #962
Alternative to #963
This enables limited use of asyncio primitives with the rclpy executor. The most important parts are the changes to how
Futureinstances handle__await__and howTaskinstances get scheduled. They allowTaskto tell when anasynciofuture isawaited, and they allowTaskto pause itself until the asyncio future completes. A consequence of this isTasknow requires an executor.There are limitations coming from asyncio not being thread safe.
Changes
Future.__await__yields itself instead ofNone- this allowsTaskto know if an rclpy Future is being awaitedExecutor.call_soon()- This is likecreate_task(), but if the callback is already a task then it won't create one. The naming is taking from asyncioExecutor._tasksis cleared every time tasks are run.Taskinstances schedule themselves when they become unblocked.Taskdetects it's waiting on an asyncio future, then it will make the asyncio future schedule theTaskwhen it's doneasyncio.sleep(0)in them already, but didn't create an asycnio event loop. Those uses have been removed.Task._lockbecause it should never run in parallel now that it schedules itself.Task.executing()because it would have needed theTask._lock, and wasn't used anywhere else.