-
Notifications
You must be signed in to change notification settings - Fork 227
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
Enable limited use of asyncio with executors #971
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
Future
instances handle__await__
and howTask
instances get scheduled. They allowTask
to tell when anasyncio
future isawait
ed, and they allowTask
to pause itself until the asyncio future completes. A consequence of this isTask
now requires an executor.There are limitations coming from asyncio not being thread safe.
Changes
Future.__await__
yields itself instead ofNone
- this allowsTask
to 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._tasks
is cleared every time tasks are run.Task
instances schedule themselves when they become unblocked.Task
detects it's waiting on an asyncio future, then it will make the asyncio future schedule theTask
when it's doneasyncio.sleep(0)
in them already, but didn't create an asycnio event loop. Those uses have been removed.Task._lock
because 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.