An asyncio wrapper around rospy I/O interfaces for Python >=3.6 to enable use of the co-operative multitasking concurrency model.
Rospy implements its I/O interfaces using callbacks and therefore handles concurrency with a preemptive multitasking model. This means that complex nodes need to worry about threading concerns such as locking (and therefore deadlocks), as well as how to structure callback-based control flow in a sane manner. If you've attempted to write a sufficiently complex rospy node that handles I/O from different sources, you probably understand how painful this can get.
Asyncio was added to the Python 3.5 standard library on a provisional bases, formalized in Python 3.6. It implements the capabilities to handle concurrency with a co-operative multitasking model. This means that a complex node can more easily manage its core loop through the use of
awaitables in a single thread. This tends to make your code straightforward to write, reason about, and debug. If you're not convinced, check out the example that handles about a dozen I/O interfaces in a single event loop.
asyncio might be for you if:
- your rospy node wants to handle I/O from numerous sources in a complex manner
- you're trying to implement a WebSocket or HTTP server in a single process alongside topics, services, and actions (if a multi-process WSGI/ASGI with IPC feels like overkill)
- you've grown to hate maintaining complex rospy nodes because of callback hell, locking noise, and deadlocking
- you're tired of having to debug complex rospy nodes where the core control flow jumps among countless threads
- you want precdictability over what will happen next and in what order
Python >= 3.6
Asyncio does not exist in Python 2 and is only provisional in Python 3.5. If you're using Xenial, you can install it using the deadsnakes ppa.
Simplifies dependency management and makes using a different version of python pretty easy. Check
requirements.txt to see what additional dependencies are used when this package is built.
Check out the
asyncio_examples/scripts folder for examples of topics, services, actions, and a composite node.
Take note that when using
asyncio together, the following boilerplate is recommended:
import aiorospy import asyncio import rospy rospy.init_node('node_name') loop = asyncio.get_event_loop() tasks = asyncio.gather( my_top_level_task(), # ... ) # Any uncaught exceptions will cause this task to get cancelled aiorospy.cancel_on_exception(tasks) # ROS shutdown will cause this task toget cancelled aiorospy.cancel_on_shutdown(tasks) try: loop.run_until_complete(tasks) except asyncio.CancelledError: pass
How it works
There's no desire to reimplement rospy this late in its life, so this package wraps the various rospy interfaces instead. This means that there are still underlying threads that handle TCP I/O for topics and services. But unlike
rospy, the API is not callbacks that run in separate threads, but rather
awaitables that run in the main thread's event loop. This is accomplished by using thread-safe intermediaries such as