C++ std::async run on main thread
I have been reading C++ Concurrency in Action and chapter four (AKA "The Chapter I Just Finished") describes a solution.
The Short Version
Have a shared std::deque<std::packaged_task<void()>>
(or a similar sort of message/task queue). Your std::async
-launched functions can push tasks to the queue, and your GUI thread can process them during its loop.
There Isn't Really a Long Version, but Here Is an Example
Shared Data
std::deque<std::packaged_task<void()>> tasks;std::mutex tasks_mutex;std::atomic<bool> gui_running;
The std::async
Function
void one_off(){ std::packaged_task<void()> task(FUNCTION TO RUN ON GUI THREAD); //!! std::future<void> result = task.get_future(); { std::lock_guard<std::mutex> lock(tasks_mutex); tasks.push_back(std::move(task)); } // wait on result result.get();}
The GUI Thread
void gui_thread(){ while (gui_running) { // process messages { std::unique_lock<std::mutex> lock(tasks_mutex); while (!tasks.empty()) { auto task(std::move(tasks.front())); tasks.pop_front(); // unlock during the task lock.unlock(); task(); lock.lock(); } } // "do gui work" std::this_thread::sleep_for(std::chrono::milliseconds(1000)); }}
Notes:
I am (always) learning, so there is a decent chance that my code is not great. The concept is at least sound though.
The destructor of the return value from
std::async
(astd::future<>
) will block until the operation launched withstd::async
completes (seestd::async
), so waiting on the result of a task (as I do in my example) inone_off
might not be a brilliant idea.You may want to (I would, at least) create your own threadsafe MessageQueue type to improve code readability/maintainability/blah blah blah.
I swear there was one more thing I wanted to point out, but it escapes me right now.
Full Example
#include <atomic>#include <chrono>#include <deque>#include <iostream>#include <mutex>#include <future>#include <thread>// shared stuff:std::deque<std::packaged_task<void()>> tasks;std::mutex tasks_mutex;std::atomic<bool> gui_running;void message(){ std::cout << std::this_thread::get_id() << std::endl;}void one_off(){ std::packaged_task<void()> task(message); std::future<void> result = task.get_future(); { std::lock_guard<std::mutex> lock(tasks_mutex); tasks.push_back(std::move(task)); } // wait on result result.get();}void gui_thread(){ std::cout << "gui thread: "; message(); while (gui_running) { // process messages { std::unique_lock<std::mutex> lock(tasks_mutex); while (!tasks.empty()) { auto task(std::move(tasks.front())); tasks.pop_front(); // unlock during the task lock.unlock(); task(); lock.lock(); } } // "do gui work" std::this_thread::sleep_for(std::chrono::milliseconds(1000)); }}int main(){ gui_running = true; std::cout << "main thread: "; message(); std::thread gt(gui_thread); for (unsigned i = 0; i < 5; ++i) { // note: // these will be launched sequentially because result's // destructor will block until one_off completes auto result = std::async(std::launch::async, one_off); // maybe do something with result if it is not void } // the for loop will not complete until all the tasks have been // processed by gui_thread // ... // cleanup gui_running = false; gt.join();}
Dat Output
$ ./messagesmain thread: 140299226687296gui thread: 140299210073856140299210073856140299210073856140299210073856140299210073856140299210073856
Are you looking for std::launch::deferred
? Passing this parameter to std::async makes the task executed on the calling thread when the get() function is called for the first time.