Files
free-file-sync-mirror/wx+/async_task.h
2025-12-10 14:38:26 -08:00

163 lines
5.2 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://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 <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: 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
{
if constexpr (std::is_same_v<ResultType, void>)
{
asyncResult_.get();
evalOnGui_();
}
else
evalOnGui_(asyncResult_.get());
}
private:
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());
std::promise<ResultType> prom;
tasks_.push_back(std::make_unique<ConcreteTask<ResultType, std::decay_t<Fun2>>>(prom.get_future(), std::forward<Fun2>(evalOnGui)));
//don't use zen::runAsync() and std::packaged_task => let exceptions crash the app directly at throw location!
std::thread([prom = std::move(prom),
fun = std::forward<Fun>(evalAsync)]() mutable
{
if constexpr (std::is_same_v<ResultType, void>)
{
fun(); //throw? => let it crash!
prom.set_value();
}
else
prom.set_value(fun()); //throw? => let it crash!
}).detach();
}
//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 of eraseIf()
eraseIf(tasks_, [&](std::unique_ptr<Task>& task)
{
if (task->resultReady())
{
readyTasks.push_back(std::move(task));
return true;
}
return false;
});
for (std::unique_ptr<Task>& 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:
explicit AsyncGuiQueue(int pollingMs = 50) :
pollingMs_(pollingMs) { timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { onTimerEvent(event); }); }
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(pollingMs_ /*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();
}
const int pollingMs_;
impl::AsyncTasks asyncTasks_;
wxTimer timer_; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu!
};
}
#endif //ASYNC_TASK_H_839147839170432143214321