Imagine I have a worker thread executing some kind of code (conceptually):
Call1(); //executes on worker thread
Call2(); //executes on worker thread
Call3(); //executes on worker thread
ret = CallThatMustExecuteOnMainThread(arg1, arg2, argN);
As the name hints CallThatMustExecuteOnMainThread
along with its arguments should be executed on the main thread and return value should be assigned to ret
. The execution flow on worker thread should be stopped while CallThatMustExecuteOnMainThread()
is being evaluated and resumed afterwards. For the sake of argument CallThatMustExecuteOnMainThread
will call some external dynamic library function. There will be only one worker thread executing the calls above.
The main thread executes the following loop (conceptually):
for (;;) {
if (hasTaskFromWorkerThread)
ExecuteTaskFromWorkerThread();
else
SomethingElse();
}
I can't quite figure out what kind of sync and data transfer mechanisms should I use to implement the required behavior above.
ADDED LATER
Here's a proof-of-concept implementation I put together.
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <optional>
#include <conio.h>
std::mutex m;
std::condition_variable cv;
std::function<int()> onMainFunc;
using ResultType = decltype(onMainFunc)::result_type;
std::optional<ResultType> result;
const std::thread::id MAIN_THREAD_ID = std::this_thread::get_id();
static int ExecOnMainImpl(int x, int y) {
return x + y;
}
struct S {
static int ExecOnMain(int x, int y) {
if (std::this_thread::get_id() == MAIN_THREAD_ID)
return ExecOnMainImpl(x, y);
else {
{
std::lock_guard<decltype(m)> lk(m);
result.reset();
onMainFunc = [x, y]() -> int { return ExecOnMainImpl(x, y); };
}
cv.notify_one();
{
std::unique_lock lk(m);
cv.wait(lk, [] { return result.has_value(); });
return result.value();
}
}
}
};
void worker_thread() {
using namespace std::chrono_literals;
for (;;) {
std::this_thread::sleep_for(5000ms);
std::cout << S::ExecOnMain(1, 2) << "\n";
}
}
int main()
{
using namespace std::chrono_literals;
auto future = std::async(std::launch::async, worker_thread);
for (;;) {
{
std::unique_lock lk(m);
if (cv.wait_for(lk, 500ms, [] { return onMainFunc != nullptr; })) {
result = onMainFunc();
onMainFunc = nullptr;
lk.unlock();
cv.notify_one();
}
else
std::cout << "Doing something else" << "\n";
}
}
future.get();
return 0;
}
This outputs:
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
3
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
3
Doing something else
Doing something else
......
Does this look sane? Any suggestions are welcome.