diff options
Diffstat (limited to 'wx+/async_task.h')
-rwxr-xr-x[-rw-r--r--] | wx+/async_task.h | 293 |
1 files changed, 143 insertions, 150 deletions
diff --git a/wx+/async_task.h b/wx+/async_task.h index 7ac03949..36d9068e 100644..100755 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -1,150 +1,143 @@ -// ***************************************************************************** -// * 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 freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef ASYNC_TASK_H_839147839170432143214321 -#define ASYNC_TASK_H_839147839170432143214321 - -#include <functional> -#include <zen/thread.h> -#include <zen/scope_guard.h> -#include <zen/stl_tools.h> -#include <wx/timer.h> - - -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); - -Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication - => don't bother, probably too many MT race conditions lurking around -*/ - -namespace impl -{ -struct Task -{ - virtual ~Task() {} - virtual bool resultReady () const = 0; - virtual void evaluateResult() = 0; -}; - - -template <class ResultType, class Fun> -class ConcreteTask : public Task -{ -public: - template <class Fun2> - ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {} - - bool resultReady () const override { return isReady(asyncResult_); } - void evaluateResult() override - { -#ifndef NDEBUG - assert(resultReady() && !resultEvaluated_); - resultEvaluated_ = true; -#endif - evalResult(IsSameType<ResultType, void>()); - } - -private: - void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); } - void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); } - - std::future<ResultType> 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 <class Fun, class Fun2> - void add(Fun&& evalAsync, Fun2&& evalOnGui) - { - using ResultType = decltype(evalAsync()); - tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(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 within the callback below - { - inRecursion_ = true; - ZEN_ON_SCOPE_EXIT(inRecursion_ = false); - - std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if - - erase_if(tasks_, [&](std::unique_ptr<Task>& 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<std::unique_ptr<Task>> tasks_; -}; -} - - -class AsyncGuiQueue : private wxEvtHandler -{ -public: - AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } - - template <class Fun, class Fun2> - void processAsync(Fun&& evalAsync, Fun2&& evalOnGui) - { - asyncTasks_.add(std::forward<Fun >(evalAsync), - std::forward<Fun2>(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_TASK_H_839147839170432143214321 +// *****************************************************************************
+// * 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 freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef ASYNC_TASK_H_839147839170432143214321
+#define ASYNC_TASK_H_839147839170432143214321
+
+#include <functional>
+#include <zen/thread.h>
+#include <zen/scope_guard.h>
+#include <zen/stl_tools.h>
+#include <wx/timer.h>
+
+
+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);
+
+Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication
+ => don't bother, probably too many MT race conditions lurking around
+*/
+
+namespace impl
+{
+struct Task
+{
+ virtual ~Task() {}
+ virtual bool resultReady () const = 0;
+ virtual void evaluateResult() = 0;
+};
+
+
+template <class ResultType, class Fun>
+class ConcreteTask : public Task
+{
+public:
+ template <class Fun2>
+ ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {}
+
+ bool resultReady () const override { return isReady(asyncResult_); }
+ void evaluateResult() override
+ {
+ evalResult(IsSameType<ResultType, void>());
+ }
+
+private:
+ void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); }
+ void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); }
+
+ std::future<ResultType> asyncResult_;
+ Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread!
+};
+
+
+class AsyncTasks
+{
+public:
+ AsyncTasks() {}
+
+ template <class Fun, class Fun2>
+ void add(Fun&& evalAsync, Fun2&& evalOnGui)
+ {
+ using ResultType = decltype(evalAsync());
+ tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(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 within the callback below
+ {
+ inRecursion_ = true;
+ ZEN_ON_SCOPE_EXIT(inRecursion_ = false);
+
+ std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if
+
+ erase_if(tasks_, [&](std::unique_ptr<Task>& 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<std::unique_ptr<Task>> tasks_;
+};
+}
+
+
+class AsyncGuiQueue : private wxEvtHandler
+{
+public:
+ AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); }
+
+ template <class Fun, class Fun2>
+ void processAsync(Fun&& evalAsync, Fun2&& evalOnGui)
+ {
+ asyncTasks_.add(std::forward<Fun >(evalAsync),
+ std::forward<Fun2>(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_TASK_H_839147839170432143214321
|