Introduction
When your HarmonyOS NEXT application needs to push work from a C/C++ worker thread back to the ArkTS event loop, the Node-API extension napi_call_threadsafe_function_with_priority() is the most direct – and safest – tool you have. It builds on the regular thread-safe-function (TSFN) mechanism, but adds two extra dials: task priority and queue position (head or tail). With those, you can fine-tune both the importance of each job and how it is blended into the ArkTS message queue, all without worrying about mutexes or race conditions.
Why a“priority TSFN” ?
- Smoother UX. A media player might treat audio-callback tasks as immediate, analytics pings as low, and thumbnail generation as idle so that the UI never stutters.
- Deterministic ordering. Sometimes you want a burst of urgent work to jump the line (enqueue at the head), while routine updates can wait their turn (enqueue at the tail).
- Zero-copy context hand-off. TSFNs let you pass an opaque void* pointer straight through to the ArkTS layer, avoiding serialization overhead.
How the flow feels in practice
1.ArkTS thread (setup)
◦ Create a TSFN linked to a JavaScript/ArkTS callback.
◦ Spin up an async-work object that will run off-thread.
2. Worker thread (execution)
◦ Inside ExecuteWork, call the priority API as many times as you like, mixing priorities and queue positions.
◦ ArkTS silently marshals those requests into its own message loop.
3. ArkTS thread (delivery)
◦ The runtime drains its queue, invoking your JS callback in the order implied by priority + head/tail.
◦ Any return value can be pulled out with napi_get_value_*
if you need it back in native code.
4. Cleanup
◦ Release the TSFN (napi_release_threadsafe_function
) and delete the async work once everything is done.
🧩 Step-by-Step: Code Integration in Sample Project
Let’s go through how to build this into your HarmonyOSNext project.
🧩 API Declaration in index.d.ts
export const callThreadSafeWithPriority: (cb: (a: number, b: number) => number) => void;
index.d.ts
🏗️napi_init.cpp
// napi_init.cpp
// ------------------------------------------------------------
// Example add-on that queues tasks back to the ArkTS main
// thread with *different* priorities using
// napi_call_threadsafe_function_with_priority.
// ------------------------------------------------------------
#include "napi/native_api.h" // Node-API declarations
#include <string.h>
#include <stdlib.h>
// -------- State container passed across callbacks ------------
struct CallbackData {
napi_threadsafe_function tsfn; // Handle to the thread-safe function
napi_async_work work; // Handle to the worker-thread job
};
// ------------------------------------------------------------
// JS-side callback runner (always executes on ArkTS main thread)
// ------------------------------------------------------------
static void CallJs(napi_env env,
napi_value jsCb,
void *context,
void *data)
{
if (env == nullptr) { // Runtime is shutting down → do nothing
return;
}
// Build arguments (12, 15) to pass into the original JS callback
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_value number1 = nullptr;
napi_create_int32(env, 12, &number1);
napi_value number2 = nullptr;
napi_create_int32(env, 15, &number2);
napi_value argv[2] = { number1, number2 };
// Invoke the callback: result = jsCb(12, 15)
napi_value resultNumber = nullptr;
napi_call_function(env, undefined, jsCb, 2, argv, &resultNumber);
// Optional: read the numeric return value for logging
int32_t res = 0;
napi_get_value_int32(env, resultNumber, &res);
}
// ------------------------------------------------------------
// Worker-thread routine — posts five tasks with varying priorities
// ------------------------------------------------------------
static void ExecuteWork(napi_env env, void *data)
{
CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
// 1) IDLE priority, enqueue at tail
napi_call_threadsafe_function_with_priority(
callbackData->tsfn, nullptr, napi_priority_idle, true);
// 2) LOW priority, enqueue at tail
napi_call_threadsafe_function_with_priority(
callbackData->tsfn, nullptr, napi_priority_low, true);
// 3) HIGH priority, enqueue at tail
napi_call_threadsafe_function_with_priority(
callbackData->tsfn, nullptr, napi_priority_high, true);
// 4) IMMEDIATE priority (highest), enqueue at tail
napi_call_threadsafe_function_with_priority(
callbackData->tsfn, nullptr, napi_priority_immediate, true);
// 5) HIGH priority, enqueue at *head* → executes before #3
napi_call_threadsafe_function_with_priority(
callbackData->tsfn, nullptr, napi_priority_high, false);
}
// ------------------------------------------------------------
// Completion callback — runs on main thread after ExecuteWork returns
// ------------------------------------------------------------
static void WorkComplete(napi_env env, napi_status status, void *data)
{
CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
// Release TSFN so runtime can clean it up when refcount hits zero
napi_release_threadsafe_function(callbackData->tsfn, napi_tsfn_release);
// Destroy the async-work handle
napi_delete_async_work(env, callbackData->work);
// Avoid dangling pointers
callbackData->work = nullptr;
callbackData->tsfn = nullptr;
}
// ------------------------------------------------------------
// Native method exposed to ArkTS: creates TSFN + async work
// ------------------------------------------------------------
static napi_value CallThreadSafeWithPriority(napi_env env,
napi_callback_info info)
{
// ---- Parse JS arguments ----
size_t argc = 1;
napi_value jsCb = nullptr; // First arg: user's JS callback
CallbackData *callbackData = nullptr;
napi_get_cb_info(env, info, &argc, &jsCb, nullptr,
reinterpret_cast<void **>(&callbackData));
// ---- Create a resource name (helps debugging tools) ----
napi_value resourceName = nullptr;
napi_create_string_utf8(env,
"Thread-safe Function Demo",
NAPI_AUTO_LENGTH,
&resourceName);
// ---- Create the thread-safe function ----
napi_create_threadsafe_function(
env,
jsCb, // JS callback to invoke
nullptr, // async_resource
resourceName, // async_resource_name
0, // unlimited queue
1, // initial refcount
callbackData, // finalize_data (not used)
nullptr, // finalize_cb
callbackData, // context passed to CallJs
CallJs, // call_js_cb
&callbackData->tsfn); // out parameter
// ---- Create async work item that will run ExecuteWork ----
napi_create_async_work(
env,
nullptr, // async_resource
resourceName,
ExecuteWork, // execute (worker thread)
WorkComplete, // complete (main thread)
callbackData, // data passed to both
&callbackData->work); // out parameter
// ---- Dispatch the work item ----
napi_queue_async_work(env, callbackData->work);
// No synchronous result
return nullptr;
}
// ------------------------------------------------------------
// Module registration boilerplate
// ------------------------------------------------------------
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
// One CallbackData instance per module instance
CallbackData *callbackData = new CallbackData();
// Define JS property: exports.callThreadSafeWithPriority = native fn
napi_property_descriptor desc[] = {
{ "callThreadSafeWithPriority",
nullptr,
CallThreadSafeWithPriority,
nullptr, nullptr, nullptr,
napi_default,
callbackData }
};
napi_define_properties(env, exports,
sizeof(desc) / sizeof(desc[0]),
desc);
return exports;
}
EXTERN_C_END
// Describe the native module for the loader
static napi_module nativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry", // import name on ArkTS side
.nm_priv = nullptr,
.reserved = { 0 },
};
// Automatic registration when the shared object is loaded
extern "C" __attribute__((constructor))
void RegisterEntryModule()
{
napi_module_register(&nativeModule);
}
napi_init.cpp
▶️Index.ets
import testNapi from 'libentry.so';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
let callback = (a: number, b: number): number => {
console.info('Callback Log, Sum = ' + (a + b))
return a + b;
}
testNapi.callThreadSafeWithPriority(callback);
})
}
.width('100%')
}
.height('100%')
}
}
Index.ets
🧮build-profile.json5 (Entry Level)
Set runtimeOnly config in arkOptions-buildOption please.
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
"abiFilters": [
"arm64-v8a",
"x86_64"
]
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
},
"nativeLib": {
"debugSymbol": {
"strip": true,
"exclude": []
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}
build-profile.json5
Practical tips & pitfalls
- Tip — batch wisely. Group related low-priority jobs into one worker; don’t spam multiple threads with idle-priority messages.
- Tip — keep callbacks lean. The ArkTS side should finish fast; long-running JS will still block the event loop regardless of priority.
- Pitfall — forgotten release. Leaking a TSFN keeps its env alive and can crash the VM on hot-reload. Always free it in the completion callback.
- Pitfall — cross-env misuse. Never pass a TSFN from one napi_env instance to another. The runtime’s multi-thread checker will abort.
When should you not use it?
If every task is trivially short or naturally ordered, the plain napi_call_threadsafe_function() is simpler and has less cognitive overhead. Likewise, heavy data shuffling may be better served by a shared ring-buffer or an IPC channel outside Node-API altogether.
Conclusion
napi_call_threadsafe_function_with_priority() is a small but powerful extension that gives your native add-on the finesse of a real-time scheduler without the usual concurrency headaches. By assigning a priority level and deciding whether to queue at the head or tail, you can keep high-value work snappy and let background tasks wait politely – all while staying 100 % thread-safe on HarmonyOS NEXT’s ArkTS runtime.
📚 Additional Resources
Document
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
Document
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
Top comments (0)