WebWorkerTaskExecutor

A task executor that runs tasks on Web Worker threads.

WebWorkerTaskExecutor.swift:111
iOS
18.0+
macOS
15.0+
tvOS
18.0+
visionOS
2.0+
watchOS
11.0+
final class WebWorkerTaskExecutor

The WebWorkerTaskExecutor provides a way to execute Swift tasks in parallel across multiple Web Worker threads, enabling true multi-threaded execution in WebAssembly environments. This allows CPU-intensive tasks to be offloaded from the main thread, keeping the user interface responsive.

Multithreading Model

Each task submitted to the executor runs on one of the available worker threads. By default, child tasks created within a worker thread continue to run on the same worker thread, maintaining thread locality and avoiding excessive context switching.

Object Sharing Between Threads

When working with JavaScript objects across threads, you must use the JSSending API to explicitly transfer or clone objects:

// Create and transfer an object to a worker thread
let buffer = JSObject.global.ArrayBuffer.function!.new(1024).object!
let transferring = JSSending.transfer(buffer)

let task = Task(executorPreference: executor) {
    // Receive the transferred buffer in the worker
    let workerBuffer = try await transferring.receive()
    // Use the buffer in the worker thread
}

Prerequisites

This task executor is designed to work with wasi-threads but it requires the following single extension: The wasi-threads implementation should listen to the message event from spawned Web Workers, and forward the message to the main thread by calling _swjs_enqueue_main_job_from_worker.

Basic Usage

// Create an executor with 4 worker threads
let executor = try await WebWorkerTaskExecutor(numberOfThreads: 4)
defer { executor.terminate() }

// Execute a task on a worker thread
let task = Task(executorPreference: executor) {
    // This runs on a worker thread
    return performHeavyComputation()
}
let result = await task.value

// Run a block on a worker thread
await withTaskExecutorPreference(executor) {
    // This entire block runs on a worker thread
    performHeavyComputation()
}

// Execute multiple tasks in parallel
await withTaskGroup(of: Int.self) { group in
    for i in 0..<10 {
        group.addTask(executorPreference: executor) {
            // Each task runs on a worker thread
            return fibonacci(i)
        }
    }
    
    for await result in group {
        // Process results as they complete
    }
}

Known limitations

Currently, the Cooperative Global Executor of Swift runtime has a bug around main executor detection. The issue leads to ignoring the @MainActor attribute, which is supposed to run tasks on the main thread, when this web worker executor is preferred.

func run(executor: WebWorkerTaskExecutor) async {
  await withTaskExecutorPreference(executor) {
    // This block runs on the Web Worker thread.
    await MainActor.run {
        // This block should run on the main thread, but it runs on
        // the Web Worker thread.
    }
  }
  // Back to the main thread.
}