Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide

Read the original article:Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide

Introduction

When your HarmonyOS NEXT application needs to push work from a C/C++ worker thread back to the ArkTS event loop, the …


This content originally appeared on DEV Community and was authored by HarmonyOS

Read the original article:Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide

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

Written by Bunyamin Eymen Alagoz


This content originally appeared on DEV Community and was authored by HarmonyOS


Print Share Comment Cite Upload Translate Updates
APA

HarmonyOS | Sciencx (2025-09-02T02:30:55+00:00) Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide. Retrieved from https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/

MLA
" » Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide." HarmonyOS | Sciencx - Tuesday September 2, 2025, https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/
HARVARD
HarmonyOS | Sciencx Tuesday September 2, 2025 » Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide., viewed ,<https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/>
VANCOUVER
HarmonyOS | Sciencx - » Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/
CHICAGO
" » Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide." HarmonyOS | Sciencx - Accessed . https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/
IEEE
" » Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide." HarmonyOS | Sciencx [Online]. Available: https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/. [Accessed: ]
rf:citation
» Node-API Part-11 : Passing Prioritised Work from Native C++ to ArkTS — a Step‑by‑Step Guide | HarmonyOS | Sciencx | https://www.scien.cx/2025/09/02/node-api-part-11-passing-prioritised-work-from-native-c-to-arkts-a-step%e2%80%91by%e2%80%91step-guide/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.