Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

HarmonyOS ArkTS Concurrency: Actors, TaskPools, and Worker Threads

Tech Oct 13 11

Actor Model Overview

ArkTS utilizes the Acter model for concurrent execution. In this architecture, computation is divided into independent actors. Each actor encapsulates its own state and behavior, communicating exclusively through asynchronous message passing. This isolation eliminates shared memory states between actors, effectively preventing common concurrency issues such as race conditions and deadlocks.

When an actor receives a message, it processes the information based on its current internal state, performs necessary calculations, potentially updates its state, and dispatches messages to other actors or itself. Since communication is asynchronous, the sender does not wait for a response, allowing true parallelism.

Data Transfer Objects

Understanding how data moves between threads is critical for safe concurrency in ArkTS. There are four primary categories of transmittable objects:

1. Normal Objects

These cannot be directly transmitted or copied across threads; they exist only within the thread that created them. Serialization relies on the structured clone algorithm.

  • Supported Types: Primitives (except Symbol), Date, String, RegExp, Array, Map, Set, simple Objects (created via {} or new Object()), ArrayBuffer, and TypedArray.
  • Limitation: Prototypes and methods are not transferred; only property values are serialized.

2. Transferable Objects

Designed for efficient bulk transfer without copying. Ownership moves from the sender to the receiver.

  • Example: ArrayBuffer.
  • Mechanism: Upon transfer, the sending thread loses access to the buffer. The receiving thread gains exclusive ownership.
  • Benefit: High performance for large data blocks by avoiding serialization overhead.
// Defining a transferable buffer
const sendBuffer = new ArrayBuffer(256);

3. Shared Objects

Allows multiple threads to access the same memory region simultaneously.

  • Example: SharedArrayBuffer.
  • Synchronization: Must use Atomics API (e.g., add, sub, and) for atomic operations to prevent data corruption.
  • Caution: Strictly requires explicit synchronization logic to avoid race conditions.
// Creating a shared buffer for multi-thread access
const sharedMem = new SharedArrayBuffer(2048);

4. Native Binding Objects

Provide direct access to underlying system capabilities like file systems or networking.

  • Key Instances:
    • Context: Provides application component context.
    • RemoteObject: Enables remote procedure calls and object reference sharing across processes.

TaskPool vs. Worker Threads

Choosing the right abstraction depends on the task requirements.

Feature Comparison

Feature TaskPool Worker
Memory Isolated per thread Isolated per thread
Data Passing Structured Clone + Transfer/Shared support Structured Clone + Transfer/Shared support
Invocation Pass function directly Send messages to entry point
Return Value Async Promise Via postMessage handlers
Lifecycle System managed (auto-scales) Developer managed
Max Threads Limited internally (approx. 4 active) Max 8 per process
Cancellation Supports queue cancellation No built-in cancellation

Execution Mechanisms

TaskPool Ideal for short-lived, independent tasks. You submit a function and parameters to the pool. The system assigns a worker thread automatically. Results return asynchronously. It abstracts away thread creation and destruction overhead.

Worker Best for long-running services that need to persist across multiple requests. A Worker instance remains alive until explicitly terminated via terminate() or close(). Communication is strictly message-based (onmessage and postMessage).

Implementation Guidelines

CPU-Bound Tasks

Use TaskPool for compute-heavy operations like image processing where splitting workloads reduces UI blockage.

import taskpool from '@ohos.taskpool';

@Concurrent
function splitProcess(chunk: ArrayBuffer) {
  // Simulate heavy calculation
  return chunk;
}

async function handleImageWork(imageData: ArrayBuffer) {
  const chunkSize = imageData.byteLength / 4;
  const chunks = [
    imageData.slice(0, chunkSize),
    imageData.slice(chunkSize, chunkSize * 2),
    imageData.slice(chunkSize * 2)
  ];

  const promises = chunks.map(chunk => {
    const t = new taskpool.Task(splitProcess, chunk);
    return taskpool.execute(t);
  });

  await Promise.all(promises);
}

I/O-Bound Tasks

For disk operations, offload synchronous blocking calls to background threads using TaskPool.

import taskpool from '@ohos.taskpool';
import fs from '@ohos.file.fs';

@Concurrent
async function writeToFile(path: string, content: string) {
  const file = await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  try {
    await fs.write(file.fd, content);
  } finally {
    fs.close(file);
  }
  return path;
}

async function batchWrite(paths: string[]) {
  const tasks = paths.map(p => new taskpool.Task(writeToFile, p, 'Content'));
  const results = await taskpool.batchExecute(tasks);
  console.info(results);
}

Synchronous Coordination

Workers can perform synchronous actions, but must ensure thread safety when accessing static resources.

// Helper.ts
export default class DataManager {
  private static instance: DataManager;
  private storage: Record<string, any> = {};

  public static getInstance(): DataManager {
    if (!DataManager.instance) { DataManger.instance = new DataManager(); }
    return DataManger.instance;
  }

  public set(key: string, value: any): void {
    this.storage[key] = value;
  }
}

// MainThread.ets
import taskpool from '@ohos.taskpool';
import DataManager from './Helper';

@Concurrent
function updateStore(id: number) {
  DataManager.getInstance().set(`id_${id}`, 'processed');
  return true;
}

async function runSyncTask() {
  const task = new taskpool.Task(updateStore, 101);
  await taskpool.execute(task);
}

Worker Lifecycle Management

Workers should be created once and reused if possible. Excessive creation degrades performance.

// Main Thread
import workerModule from '@ohos.worker';

const myWorker = new workerModule.ThreadWorker('entry/ets/workers/background.ts');

myWorker.onmessage = (e) => {
  console.log('Got result:', e.data);
};

myWorker.postMessage({ action: 'start' });

// Cleanup
myWorker.terminate();

// Inside Worker.ts
import workerModule from '@ohos.worker';
const scope = workerModule.workerPort;

scope.onmessage = (e) => {
  if (e.data.action === 'start') {
    // Do work
    scope.postMessage({ status: 'ready' });
  }
};

// To exit manually
scope.close();

Note: TaskPool functions marked with @Concurrent require the code to reside in .ets files. Arguments passed to these functions must be serializable according to the rules defined in the Data Transfer section.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.