Skip to content

Async Processing Example

This example demonstrates how to use GDSignalBus's async signal functionality to handle time-consuming operations without blocking the main thread.

Complete Example Code

gdscript
@warning_ignore_start("static_called_on_instance")
extends Node

# Async signal example
# Demonstrates how to use async callbacks to handle time-consuming operations

@onready var bus = SignalBus.get_singleton()

func _ready():
	print("\n=== Async Example Started ===")
	bus.set_debug_enabled(true)
	
	# Example 1: Basic async subscription
	print("Subscribing to async_event...")
	bus.subscribe_async("async_event", func(message):
		print("[Async Thread] Processing message: ", message)
		# Simulate time-consuming operation
		OS.delay_msec(3000)
		print("[Async Thread] Processing complete")
	)
	
	# Example 2: Async subscription with callbacks
	var async_callback = func(data):
		print("[Async Thread] Starting data processing: ", data)
		OS.delay_msec(5000)
		return "Processing result: " + str(data * 2)
	
	var complete_callback = func(result):
		print("[Main Thread] Processing complete, result: ", result)
	
	var error_callback = func(error):
		print("[Main Thread] Processing failed: ", error)
	
	# Subscribe to async signal with completion and error callbacks
	print("Subscribing to data_processing...")
	bus.subscribe_async_with_callbacks(
		"data_processing",
		async_callback,
		complete_callback,
		error_callback
	)
	print("=== Subscriptions Complete ===\n")

func _process(_delta):
	# Process completed async tasks in the main thread
	bus.process_async_tasks()
	if Input.is_action_just_pressed("ui_up"):
		# Emit async signal
		print("[Main Thread] Emitting async signal...")
		bus.emit_async("async_event", ["Hello Async!"])
		print("[Main Thread] Continuing execution (not waiting)")
	if Input.is_action_just_pressed("ui_down"):
		# Emit signal
		print("[Main Thread] Emitting data processing signal...")
		bus.emit_async("data_processing", [42])
		print("[Main Thread] Continuing execution")

func _exit_tree():
	print("\n=== _exit_tree called ===")
	print("Active async tasks: ", bus.get_active_async_task_count())
	print("=== _exit_tree finished ===\n")

Feature Description

1. Basic Async Subscription

gdscript
bus.subscribe_async("async_event", func(message):
	print("[Async Thread] Processing message: ", message)
	# Simulate time-consuming operation
	OS.delay_msec(3000)
	print("[Async Thread] Processing complete")
)

2. Async Subscription with Callbacks

gdscript
# Define async processing function
var async_callback = func(data):
	print("[Async Thread] Starting data processing: ", data)
	OS.delay_msec(5000)
	return "Processing result: " + str(data * 2)

# Define completion callback
var complete_callback = func(result):
	print("[Main Thread] Processing complete, result: ", result)

# Define error callback
var error_callback = func(error):
	print("[Main Thread] Processing failed: ", error)

# Subscribe to async signal with callbacks
bus.subscribe_async_with_callbacks(
	"data_processing",
	async_callback,
	complete_callback,
	error_callback
)

3. Async Signal Emission

gdscript
# Emit async signals
bus.emit_async("async_event", ["Hello Async!"])
bus.emit_async("data_processing", [42])

4. Async Task Processing

gdscript
func _process(_delta):
	# Process completed async tasks in the main thread
	bus.process_async_tasks()

Control Instructions

After running the example, you can use the following keys to test async functionality:

  • UI Up (Up arrow): Emit async_event async signal, triggering 3-second async processing
  • UI Down (Down arrow): Emit data_processing async signal, triggering 5-second data processing

Output Example

Basic Async Signal Output

[Main Thread] Emitting async signal...
[Main Thread] Continuing execution (not waiting)
[Async Thread] Processing message: Hello Async!
# After 3 seconds
[Async Thread] Processing complete

Async Signal with Callbacks Output

[Main Thread] Emitting data processing signal...
[Main Thread] Continuing execution
[Async Thread] Starting data processing: 42
# After 5 seconds
[Main Thread] Processing complete, result: Processing result: 84

Cleanup Output

=== _exit_tree called ===
Active async tasks: 0
=== _exit_tree finished ===

Core Concepts

Async Processing Flow

  1. Subscription Phase: Use subscribe_async() or subscribe_async_with_callbacks() to subscribe to async signals
  2. Emission Phase: Use emit_async() to emit async signals, returns immediately without waiting
  3. Processing Phase: Async thread executes time-consuming operations
  4. Completion Phase: Calls completion callbacks in the main thread (if provided)
  5. Cleanup Phase: Process completed async tasks in _process()

Async vs Sync

  • Sync: emit() blocks until all subscribers complete processing
  • Async: emit_async() returns immediately, processing happens in the background

Callback Mechanism

  • Async Callback: Executed in async thread, used for time-consuming operations
  • Completion Callback: Executed in main thread, used for handling results
  • Error Callback: Executed in main thread, used for handling errors

Task Management

gdscript
# Get active async task count
bus.get_active_async_task_count()

# Process completed async tasks (must be called in _process)
bus.process_async_tasks()

Best Practices

1. Always Call process_async_tasks()

gdscript
func _process(_delta):
    bus.process_async_tasks()  # Must be called every frame

2. Use Callbacks Appropriately

gdscript
# For simple async operations, use basic subscription
bus.subscribe_async("simple_task", async_handler)

# For operations that need result handling, use subscription with callbacks
bus.subscribe_async_with_callbacks("complex_task", 
    async_handler, 
    complete_callback, 
    error_callback
)

3. Avoid Long Blocking Operations

gdscript
# Break long operations into small chunks
func long_running_task(data):
    for i in range(100):
        process_chunk(data[i])
        if i % 10 == 0:  # Check every 10 chunks
            OS.delay_msec(1)  # Yield control

4. Error Handling

gdscript
# Always provide error callbacks
var error_callback = func(error):
    print("Async operation failed: ", error)
    # Implement fallback strategy

bus.subscribe_async_with_callbacks("risky_task",
    async_handler,
    complete_callback,
    error_callback
)

This async example demonstrates how to handle time-consuming operations without blocking the main thread, which is an important tool for building responsive game applications.

Released under the MIT License