Read the original article:Code Execution Restrictions in Worker Threads
Requirement Description
Move a time-consuming task (JSON parsing, HTTP requests, dbStore operations, eventHub posting, preferences, @Observer, and singleton access) from the main thread to a worker thread and ensure it executes normally without interruption or UI lag.
Background Knowledge
- HarmonyOS worker threads have restrictions compared to the main thread. Not all APIs and libraries are thread-safe.
- Cross-thread singletons must follow shared module rules ("use shared") and classes should be marked with @Sendable.
- Certain operations (database, context-dependent APIs, UI modification) cannot directly run in worker threads.
- Locks such as AsyncLock are necessary to protect shared data consistency.
Implementation Steps
- Identify which APIs are not supported in worker threads (e.g., dbStore, preferences, UI modification).
- Move heavy computation (parsing, calculations) into a worker thread, while keeping UI and restricted operations on the main thread.
- For shared data (e.g., singleton), create a shared module and mark classes as @Sendable.
- Use ArkTSUtils.locks.AsyncLock to protect concurrent modifications.
- Pass context or necessary references from the main thread to the worker thread when required.
Code Snippet / Configuration
Below is the demo shared for a @Sendable singleton ensuring uniqueness across threads:
import { ArkTSUtils , collections } from '@kit.ArkTS';
// Declare that the current module is a shared module and can only export Sendable data
"use shared"
// Shared module, TestData is globally unique
@Sendable
export class TestData {
private keyHandle: string = '0';
private count_: number = 0;
private arr: collections.Array<number> = new collections.Array<number>();
private isLoadingShowing: boolean = false;
lock_: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock()
private static instance: TestData;
private constructor() {}
public static getInstance(): TestData {
if (TestData.instance == null) {
TestData.instance = new TestData();
console.debug('getInstance new')
}
return TestData.instance;
}
getLoadingShowing(): boolean {
return this.isLoadingShowing;
}
setLoadingShowing(isShow: boolean) {
this.isLoadingShowing = isShow
}
async getKeyHandle(): Promise<string> {
return this.lock_.lockAsync(() => {
return this.keyHandle;
})
}
async setKeyHandle(keyHandle: string) {
await this.lock_.lockAsync(() => {
this.keyHandle = keyHandle;
})
}
public async getCount(): Promise<number> {
return this.lock_.lockAsync(() => {
return this.count_;
})
}
public async increaseCount() {
await this.lock_.lockAsync(() => {
this.count_++;
})
}
public async getArr(): Promise<collections.Array<number>> {
return this.lock_.lockAsync(() => {
return this.arr;
})
}
public async setArr(arr: collections.Array<number>) {
return this.lock_.lockAsync(() => {
this.arr = arr;
})
}
}
Test Results
- Singleton instance (TestData.getInstance()) works correctly across multiple threads.
- State changes (count_, arr, keyHandle) are thread-safe using AsyncLock.
- Heavy computation can be executed in the worker thread without blocking the UI.
Limitations or Considerations
- Some APIs (e.g., dbStore, preferences, eventHub, UI updates) cannot be directly executed in worker threads. These must remain on the main thread or be proxied.
- @Sendable and "use shared" must be applied to classes/modules to guarantee thread safety.
- Context must be explicitly passed to worker threads from the main thread.
- Code refactoring may be required if many main-thread-only APIs are mixed inside worker logic.
Top comments (0)