Skip to content

TDAsyncIO

TouchDesigner typically runs Python scripts synchronously on its main thread. If a script takes a long time to execute (e.g., waiting for a network request, performing heavy computation, or handling file I/O), it blocks the main thread, causing the entire TouchDesigner interface and process to freeze.

Python’s asyncio library provides a way to write asynchronous code using async and await, allowing long-running tasks to run concurrently without blocking. However, integrating asyncio’s event loop directly into TouchDesigner’s frame-based execution requires careful management.

TDAsyncIO solves this problem by providing a managed asyncio event loop that runs incrementally within the TouchDesigner frame cycle. It allows you to launch asynchronous tasks (coroutines) and monitor their progress without freezing your project.

Within LOPs, TDAsyncIO is a crucial utility, often embedded within other components (like ChatTD) to handle operations like API calls that would otherwise block the main thread.

  • Event Loop: TDAsyncIO manages an asyncio event loop. In each TouchDesigner frame where the component cooks, it runs one iteration of the event loop, allowing scheduled tasks to advance.
  • Tasks: You submit asynchronous functions (coroutines defined with async def) to the manager using the Run method. Each submission becomes a tracked task.
  • Task Lifecycle: Tasks progress through statuses: pending -> running -> completed / failed / cancelled / timeout.
  • Task Table: An internal Table DAT (task_table) provides visibility into all managed tasks, showing their ID, status, description, duration, start/end times, and any errors.

The primary way to use TDAsyncIO is via its Python extension, typically accessed through an operator reference.

Python Example:

import asyncio
# Get a reference to the TDAsyncIO component
td_asyncio_op = op('path/to/TDAsyncIO') # Adjust path as needed
# Define an asynchronous function (coroutine)
async def wait_and_print(duration, message):
print(f"Task started: Waiting for {duration} seconds...")
await asyncio.sleep(duration) # Non-blocking wait
print(f"Task finished: {message}")
return f"Completed after {duration} seconds"
# --- Running a single task ---
print("Submitting single task...")
task_id_1 = td_asyncio_op.ext.AsyncIOManager.Run(
coroutines=wait_and_print(3, "Hello from async task 1!"),
description="Simple Wait Task",
info={'custom_data': 'value1'},
timeout=10 # Optional: Cancel if not done in 10 seconds
)
print(f"Submitted task with ID: {task_id_1}")
# --- Running multiple tasks ---
print("Submitting multiple tasks...")
task_id_list = td_asyncio_op.ext.AsyncIOManager.Run(
coroutines=[
wait_and_print(2, "Task 2 finished!"),
wait_and_print(4, "Task 3 finished!")
],
description="Batch Wait Tasks" # Shared description
)
# Note: Run returns the ID of the *last* task submitted in the list
print(f"Submitted batch, last task ID: {task_id_list}")
# --- Getting a result (example) ---
# Note: You would typically check task status or use callbacks
# This is just a basic example, results are available after completion
# result = td_asyncio_op.ext.AsyncIOManager.GetTaskResult(task_id_1)
# if result:
# print(f"Result for task {task_id_1}: {result}")

Key Points:

  • Use async def to define your asynchronous functions.
  • Use await inside async def functions for operations that would normally block (like time.sleep, network requests with libraries like aiohttp, etc.). Use asyncio.sleep() for non-blocking delays.
  • Pass the coroutine object (the result of calling your async def function) to the Run method.
  • The Run method takes the following arguments:
    • coroutines: A single coroutine or a list/tuple of coroutines.
    • description (Optional[str]): A description shown in the task table.
    • info (Optional[dict]): Additional metadata stored with the task and shown in the table.
    • timeout (Optional[float]): Time in seconds after which the task will be automatically cancelled if still running.
Cleanup Age (sec) (Clearafter) op('tdasyncio').par.Clearafter Float
Default:
600
Cancel Active Tasks (Cancelactive) op('tdasyncio').par.Cancelactive Pulse
Default:
None
Cancel & Clear All (Clearall) op('tdasyncio').par.Clearall Pulse
Default:
None
Clear Finished Tasks (Clearfinished) op('tdasyncio').par.Clearfinished Pulse
Default:
None

Inside the TDAsyncIO component, there is a Table DAT named task_table which displays the status of all managed asynchronous tasks. Key columns include:

  • task_id: Unique identifier for the task.
  • status: Current state (pending, running, completed, failed, cancelled, timeout).
  • description: The description provided when the task was run.
  • duration: Time elapsed since the task started (for running tasks) or total execution time (for finished tasks).
  • created_at, completed_at: Timestamps for task start and end.
  • error: Error message if the task failed.
  • info: Additional metadata provided when the task was run.

This table is useful for monitoring the progress and outcome of your asynchronous operations.

  • ChatTD - Integrates TDAsyncIO internally to handle asynchronous LLM API calls.