How to emit_signal from WorkerThreadPool or Thread in C++?

Godot Version

4.2

Question

I try to offload some work to thread and get signal on finish, but get error when signaling from thread:

  1. Implemented part in C++
WorkerThreadPool::TaskID GDExample::run_pool() {
    this->connect("task_completed", callable_mp(this, &GDExample::task123_completed), CONNECT_DEFERRED); // , CONNECT_DEFERRED !
    auto call = callable_mp(this, &GDExample::task123).bind(777);
    auto id = WorkerThreadPool::get_singleton()->add_task(call);
    return id;
}

bool GDExample::check_pool(WorkerThreadPool::TaskID id) {
    auto err = WorkerThreadPool::get_singleton()->wait_for_task_completion(id);
    return err == Error::OK;
}

void GDExample::task123(int i) {
    //set_current_thread_safe_for_nodes(true);
    UtilityFunctions::print("task123 ", i);
    emit_signal("task_completed", i);
}

void GDExample::task123_completed(int par) {
    UtilityFunctions::print("task123 completed", par);
}
  1. Registered function in bind:
ClassDB::bind_method(D_METHOD("run_pool"), &GDExample::run_pool);
ClassDB::bind_method(D_METHOD("check_pool"), &GDExample::check_pool);
ClassDB::bind_method(D_METHOD("task123_completed"), &GDExample::task123_completed);
  1. And call from GDScript:
func _ready():
	var id = run_pool()
	var res = check_pool(id)

Everything works as expected except signal:

E 0:00:01:0617   emit_signalp: Caller thread can't call this function in this node (/root/Node2D/GDExample). Use call_deferred() or call_thread_group() instead.
  <C++ Error>    Condition "!is_accessible_from_caller_thread()" is true. Returning: (ERR_INVALID_PARAMETER)
  <C++ Source>   scene/main/node.cpp:3638 @ emit_signalp()

This message came from ERR_THREAD_GUARD, which uses is_accessible_from_caller_thread(), which check is my node not in tree or is_current_thread_safe_for_nodes().

So questions are:

  1. Is it possible emit_signal on main thread (I believe CONNECT_DEFERRED was made for that) ?
  2. And how to mark thread safe for node if set_current_thread_safe_for_nodes(bool) is not exposed ? I see many usage of this function in godot sources.

Thank you.

There is nothing thread-safe about nodes and their related signals.

If you have the luxury to work on the C++ side do yourself a favor and pretend that nodes do not exist inside your thread context.

So always have a layer inbetween that collects from the nodes what you need and gives it to the thread, and have also a layer that collects from and syncs with the thread and gives it back to the node on the main thread and does all the “I am done” updates and signals.

I found solution if somebody is interested.
We have to change:
emit_signal("task_completed", i);
to
call_deferred("emit_signal", "task_completed", i);

WorkerThreadPool::TaskID GDExample::run_pool() {
    this->connect("task_completed", callable_mp(this, &GDExample::task123_completed), CONNECT_DEFERRED); // , CONNECT_DEFERRED !
    auto call = callable_mp(this, &GDExample::task123).bind(777);
    auto id = WorkerThreadPool::get_singleton()->add_task(call);
    return id;
}

bool GDExample::check_pool(WorkerThreadPool::TaskID id) {
    auto err = WorkerThreadPool::get_singleton()->wait_for_task_completion(id);
    return err == Error::OK;
}

void GDExample::task123(int i) {
    //set_current_thread_safe_for_nodes(true);
    UtilityFunctions::print("task123 ", i);
    //emit_signal("task_completed", i); // Cnange this
    call_deferred("emit_signal", "task_completed", i); // To this
}

void GDExample::task123_completed(int par) {
    UtilityFunctions::print("task123 completed", par);
}