Async Task Execution in Isaac Sim

Isaac Sim Version

4.2.0

Isaac Lab Version (if applicable)

1.2

Operating System

Ubuntu 24.04

GPU Information

  • Model: GeForce RTX 4090
  • Driver Version: 550.107.02

Description

While developing a robotics application with Isaac Sim, I encountered a challenge with asynchronous task execution.

Current Behavior:

  1. When submitting async tasks using omni.kit.async_engine.run_coroutine, the tasks don’t execute automatically
  2. Tasks only progress when manually calling omni.kit.app.get_app().update()
  3. Async tasks remain pending until explicit call to omni.kit.app.get_app().update()

Example Code:

from omni.kit.async_engine import run_coroutine
from omni.kit.app import get_app

# Submit async task
task = run_coroutine(some_async_function())

# Must manually advance event loop
while not task.done():
    get_app().update()

Problem:

  • Network I/O async operations should be non-blocking
  • Main thread cannot perform other work while waiting

Expected Behavior:
Async tasks should execute in the background while the main thread performs other operations, without requiring explicit app.update()calls.

# Submit network request
task = run_coroutine(async_http_request())

# Continue other work while I/O happens in background
do_robot_control()

# Get result when needed
result = task.result()

Questions:

  1. Is this the intended behavior for async task execution?
  2. Are there better approaches to handle async tasks in Isaac Sim?
  3. How can we ensure async task execution without impacting main thread performance?

@siky47 quick question, why not use the python async (asyncio library) instead of the omni.kit.async_engine?

@danielle.sisserman

It seems that Isaac Sim’s kit has already created an event loop. When I attempt to create a new event loop using asyncio, it results in the following AssertionError:

Traceback (most recent call last):
  File "/home/ps/miniconda3/envs/isaaclab_isaac4.2/lib/python3.10/site-packages/omni/extscore/omni.kit.async_engine/omni/kit/async_engine/async_engine.py", line 262, in <lambda>
    lambda *args: self._loop.run_once(),
  File "/home/ps/miniconda3/envs/isaaclab_isaac4.2/lib/python3.10/site-packages/omni/extscore/omni.kit.async_engine/omni/kit/async_engine/async_engine.py", line 193, in run_once
    assert events._get_running_loop() is self
AssertionError

Here’s the code I used to run the async task with asyncio:

try:
    loop = asyncio.get_running_loop()
except RuntimeError:
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
task = loop.run_until_complete(v)

I’m uncertain whether this AssertionError could cause potential issues. So I tried switching from asyncio to omni.kit.async_engine

Here is somehting I wrote and is working for me:

    async def run(self):

        try:
            await self.async_runner()

        except asyncio.CancelledError:
            carb.log_error("Task was cancelled")
            raise
                     
        except Exception as e:
            carb.log_error(f"An error occurred: {e}")
            task = asyncio.current_task()
            task.cancel()

and you have to call:

self.task = asyncio.ensure_future(self.run())

and this is the class which has the async_runner:


class AsyncSignal:
    def __init__(self, signal_name: str, num_of_steps: int, randomization_data: List[RandomizationSignalData], subframes :int = 40, writer = None):
        self.signal_name = signal_name
        self.signal_calls = 0
        self.subframes = subframes
        self.num_of_steps = num_of_steps
        self.writer = writer
        self.randomization_data = randomization_data


    
    async def async_runner(self):
        for i in range(self.num_of_steps):  
            try:

                carb.log_error(f"frame number {i}")

                # call all my randomization functions (ex: tree.randomize(), car.randomize()...each kind of randomization is a RandomizationSignalData with it's own frequency)
                for data in self.randomization_data:
                    if data.check_frequency(self.signal_calls): #this way can controll frequency of function calls
                        await data.execute_randomization()

                #call write function of my custom writer to save camera view
                if self.writer is not None:
                    self.writer.schedule_write()

                await rep.orchestrator.step_async(rt_subframes=self.subframes)
                await omni.kit.app.get_app().next_update_async()
                carb.log_error(f"step async... ")

                self.signal_calls += 1



            except Exception as e:
                carb.log_error(f"An error occurred: {e}")
                task = asyncio.current_task()
                task.cancel()
            except asyncio.CancelledError:
                # This block will run if the task is cancelled, allowing you to break the loop
                carb.log_error(f"Task {self.signal_name} was cancelled")
                break

        await omni.kit.app.get_app().next_update_async()
        rep.orchestrator.stop()


and this is my RandomizationSignalData class:

class RandomizationSignalData:
    def __init__(self, async_delegate, frequency):
        self.async_delegate = async_delegate 
        self.frequency = frequency

    def check_frequency(self, signal_calls):
        return signal_calls % self.frequency == 0
    
    async def execute_randomization(self):
        if self.async_delegate is not None:
            await self.async_delegate()

I really hope i helped

this is an example I got from someone at nvidia but I remember having issues with it. you can try:

async def run_randomization(num_frames=10):
    for _ in range(num_frames):
        await rep.orchestrator.step_async(rt_subframes=10)

    await omni.kit.app.get_app().next_update_async()
    rep.orchestrator.stop()

num_frames = 50
asyncio.ensure_future(run_randomization(num_frames))

Hi @danielle.sisserman,

Thank you for your assistance!!

I’ve tried using asyncio.ensure_future(v) to submit the async task in the main thread, but the issue persists. It seems I need to call omni.kit.app.get_app().update() (or possibly a similar stepping function like env.sim.step()) for the async event loop to progress. Without this, the async function doesn’t execute as expected.

Here’s a code snippet illustrating the problem:

task = asyncio.ensure_future(async_func)
while not async_func.done():
    # get_app().update()
    pass
# The loop never finishes because async_func remains pending.

When I include the update call, the async_func executes normally:

task = asyncio.ensure_future(async_func)
while not async_func.done():
    omni.kit.app.get_app().update()

I found some relevant information in omni.isaac.lab.sim.simulation_context.py

The simulation context can be used in two different modes of operations:
1. Standalone python script: In this mode, the user has full control over the simulation and
can trigger stepping events synchronously (i.e. as a blocking call). In this case the user
has to manually call :meth:step step the physics simulation and :meth:render to
render the scene.
2. Omniverse extension: In this mode, the user has limited control over the simulation stepping
and all the simulation events are triggered asynchronously (i.e. as a non-blocking call). In this
case, the user can only trigger the simulation to start, pause, and stop. The simulation takes
care of stepping the physics simulation and rendering the scene.

This might not be directly related to the issue, but any further insights would be greatly appreciated!

The code I shared with you is for an extension. you could of course make some small changes and run it in headless mode. I havent done that.

Any ways, I recommand you try running the code I’ve shared. It is the correct way to run async loops in an omniverse extention.

Thank you so much for your help! However, I need to run my script in Standalone Python script mode.