Skip to content
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

async wait/sleep implementation #1234

Open
russkel opened this issue Feb 29, 2024 · 5 comments
Open

async wait/sleep implementation #1234

russkel opened this issue Feb 29, 2024 · 5 comments

Comments

@russkel
Copy link
Contributor

russkel commented Feb 29, 2024

Feature request

Feature description

I have been writing some hardware "test" scripts used for checking the integration of hardware in real time (not using any test framework or assertions but simply stimulating a system with inputs and manually confirming the hardware is performing as expected). I have found writing this using async has made things much simpler and nicer:

e.g.

    async def test_1(self):
        self.get_logger().info("Setting Rudder to Center")
        await self.set_and_wait_until_rudder_angle(0.0, 5.0)

        self.get_logger().info("Setting Rudder to Full Port")
        await self.set_and_wait_until_rudder_angle(-1.0, 5.0)

To which I kick off like so:

def main(args=None):
    rclpy.init(args=args)
    executor = SingleThreadedExecutor()
    node = TestNode()
    executor.add_node(node)

    try:
        node.get_logger().info("Starting Test 1")
        task = executor.create_task(node.test_1())
        executor.spin_until_future_complete(task)

        node.get_logger().info("Testing complete!")
    except KeyboardInterrupt:
        pass

    node.destroy_node()
    executor.shutdown()

Now there is pretty much no examples of create_task being used like this so I have been experimenting and reading a bit of the rclpy code. After looking at the executor code in depth I couldn't find out how to use the executor to do an equivalent to await asyncio.sleep. I had a quick look at how asyncio implemented theirs and I came up with this hack using a Timer:

    async def async_wait_for(self, rel_time: float):
        fut = Future()
        timer = None

        def done_waiting():
            fut.set_result(None)
            timer.cancel()

        timer = self.create_timer(rel_time, done_waiting)
        await fut

This was the only missing piece and I was able to asynchronously "wait" and write the co-routines as expected.

My request is something like this is added to the Executor class, perhaps using a more elegant interface than abusing a Timer like this.

#279 might be related.

@arjo129
Copy link

arjo129 commented Feb 29, 2024

I like this idea. I recently ran into a similar need. My solution was spin_until_future_complete has a timeout option which I abused with a default empty future that never gets set. It probably is not great but 🤷.

@russkel
Copy link
Contributor Author

russkel commented Feb 29, 2024

I like this idea. I recently ran into a similar need. My solution was spin_until_future_complete has a timeout option which I abused with a default empty future that never gets set. It probably is not great but 🤷.

If I am not mistaken that will block the thread you're in? How do you use that to await in a co-routine?

@arjo129
Copy link

arjo129 commented Mar 1, 2024

Yeah it does. I wasnt using async await so... If an async/await option were available I'd probably have prefered that. Just echoing the sentiment that an API like this would be useful for people.

@russkel
Copy link
Contributor Author

russkel commented Mar 1, 2024

Yeah it does. I wasnt using async await so... If an async/await option were available I'd probably have prefered that. Just echoing the sentiment that an API like this would be useful for people.

Yep I understood what you meant, I was just wondering if I had missed something.

@jmblixt3
Copy link
Contributor

jmblixt3 commented Sep 15, 2024

I like this idea. I recently ran into a similar need. My solution was spin_until_future_complete has a timeout option which I abused with a default empty future that never gets set. It probably is not great but 🤷.

If I am not mistaken that will block the thread you're in? How do you use that to await in a co-routine?

I was also looking into this, and looks like since #1316 spin_until_future_complete now works in callbacks. I'm still not a fan of relying on an empty future never finishing though as the "right" way to do this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants