// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef ASYNC_JOB_839147839170432143214321 #define ASYNC_JOB_839147839170432143214321 #include #include #include #include #include namespace zen { /* Run a task in an async thread, but process result in GUI event loop ------------------------------------------------------------------- 1. put AsyncGuiQueue instance inside a dialog: AsyncGuiQueue guiQueue; 2. schedule async task and synchronous continuation: guiQueue.processAsync(evalAsync, evalOnGui); */ namespace impl { struct Task { virtual ~Task() {} virtual bool resultReady () const = 0; virtual void evaluateResult() = 0; }; template class ConcreteTask : public Task { public: template ConcreteTask(std::future&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward(evalOnGui)) {} bool resultReady () const override { return isReady(asyncResult_); } void evaluateResult() override { #ifndef NDEBUG assert(resultReady() && !resultEvaluated_); resultEvaluated_ = true; #endif evalResult(IsSameType()); } private: void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); } void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); } std::future asyncResult_; Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread! #ifndef NDEBUG bool resultEvaluated_ = false; #endif }; class AsyncTasks { public: AsyncTasks() {} template void add(Fun&& evalAsync, Fun2&& evalOnGui) { using ResultType = decltype(evalAsync()); tasks.push_back(std::make_unique>(zen::runAsync(std::forward(evalAsync)), std::forward(evalOnGui))); } //equivalent to "evalOnGui(evalAsync())" // -> evalAsync: the usual thread-safety requirements apply! // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance! void evalResults() //call from gui thread repreatedly { if (!inRecursion) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one via the callback below { inRecursion = true; ZEN_ON_SCOPE_EXIT(inRecursion = false); std::vector> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if erase_if(tasks, [&](std::unique_ptr& task) { if (task->resultReady()) { readyTasks.push_back(std::move(task)); return true; } return false; }); for (auto& task : readyTasks) task->evaluateResult(); } } bool empty() const { return tasks.empty(); } private: AsyncTasks (const AsyncTasks&) = delete; AsyncTasks& operator=(const AsyncTasks&) = delete; bool inRecursion = false; std::vector> tasks; }; } class AsyncGuiQueue : private wxEvtHandler { public: AsyncGuiQueue() { timer.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } template void processAsync(Fun&& evalAsync, Fun2&& evalOnGui) { asyncTasks.add(std::forward(evalAsync), std::forward(evalOnGui)); if (!timer.IsRunning()) timer.Start(50 /*unit: [ms]*/); } private: void onTimerEvent(wxEvent& event) //schedule and run long-running tasks asynchronously { asyncTasks.evalResults(); //process results on GUI queue if (asyncTasks.empty()) timer.Stop(); } impl::AsyncTasks asyncTasks; wxTimer timer; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu! }; } #endif //ASYNC_JOB_839147839170432143214321