Cross-thread operations in C++ without SendMessage

1k views Asked by At

I'm looking for a way to perform cross-thread operations the way SendMessage allows. In other words, how to have a thread execute some function in another thread. But I want to do it without SendMessage as that requires a window which is not always available.

Synchronously or asynchronously is fine.

.NET does it with the System.Windows.Threading.Dispatcher class, so surely there's a way?

1

There are 1 answers

6
David Haim On BEST ANSWER

So I'm guessing we're talking about Windows OS here, right? you should specify that in your question. A solution for Windows might be a different for a solution for Linux, for example.

now, regarding your question, any solution for that situation will force your thread(s) to either wait on some event to happen (enqueuing a task), or poll for a task endlessly.

So we're talking about either some kind of a mutex, a condition variable or some special sleeping function.

One (simple) and non portable way or sending "tasks" to other threads is to use the builtin Win32 - mechanism of APC (Asynchronous Procedure Call).

it utilizes the functions QueueUserAPC and SleepEx, I have mini tested this solution on my Windows 10 + Visual studio 2015

namespace details {
    void waitforTask() noexcept {
        SleepEx(INFINITE, TRUE);
    }

    void __stdcall executeTask(ULONG_PTR ptr) {
        if (ptr == 0) {
            return;
        }
        std::unique_ptr<std::function<void()>> scopedPtr(reinterpret_cast<std::function<void()>*>(ptr));
        (*scopedPtr)();
    }
}

template<class F, class ... Args>
void sendTask(void* threadHandle, F&& f, Args&& ... args) {
    auto task =
        std::make_unique<std::function<void()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

    const auto res = QueueUserAPC(&details::executeTask,
        threadHandle,
        reinterpret_cast<ULONG_PTR>(task.get()));
    if (res == 0) {
        throw std::runtime_error("sendTask failed.");
    }

    task.release();
}

Example use:

std::thread thread([] {
        for (;;) {
            details::waitforTask();
        }
    });

   sendTask(thread.native_handle(), [](auto literal) {
        std::cout << literal;
   }, "hello world");

this solution also shows how to use Win32 without actually contaminating the business logic written in C++ code non related win32 code.

this solution also can be adapted to a cross platform solution, instead of using an internal, semi-documented win32 task queue, one can build his own task queue using std::queue and std::function<void()>. instead of sleeping in alertable state, one can use std::condition_variable instead. this is what any thread-pool does behind the scenes in order to get and execute tasks. If you do want a cross-platform solution, I suggest googling "C++ thread pool" in order to see examples of such task queue.