Попробуйте это:
#include <iostream>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <tuple>
template<class... Args>
class EventTask
{
public:
EventTask(std::function<void(Args...)> func) : m_Function(func), m_Run(true) {
m_thread = std::thread{ [=]() {
taskFunc();
}};
}
~EventTask() {
stop();
m_thread.join();
}
void notify(const std::tuple<Args...> &args) {
std::unique_lock<std::mutex> lock(_mutex);
_signalled = true;
_args = args;
_condvar.notify_all();
}
void stop() {
m_Run = false;
_condvar.notify_all();
}
private:
void taskFunc()
{
std::tuple<Args...> args;
while (true){
{
std::unique_lock<std::mutex> lock(_mutex);
_condvar.wait(lock, [&] { return m_Run && _signalled; });
if (!m_Run) break;
_signalled = false;
args = _args;
}
std::apply(m_Function, args);
//_EventFinished.notify();
}
}
private:
std::function<void(Args...)> m_Function;
std::tuple<Args...> _args;
std::mutex _mutex;
std::condition_variable _condvar;
bool _signalled = false;
//Event _EventFinished;
bool m_Run;
std::thread m_thread;
};
int main()
{
EventTask<int, int> ma{ [](int a, int b) {
}};
ma.notify({ 1, 2 });
}
Что здесь происходит? Существует два потока: поток «производителя» (тот, который создает аргументы для функции, а следовательно, и имени) и поток «потребителя» (тот, который фактически выполняет).
Поток "продюсер" блокирует мьютекс, копирует аргументы и уведомляет, что нужно что-то сделать. «потребительский» поток блокирует мьютекс, затем ожидает при условии. Ожидание при условии (и мьютекс) освобождает мьютекс, который будет получен при получении уведомления о переменной условия. Когда переменная «продюсер» устанавливает аргументы, «потребитель» проснется, повторно получит мьютекс (это необходимо, в противном случае «производитель» может установить аргументы дважды подряд, приводя к гонке, что является неопределенным поведением), один раз снова копирует аргументы и освобождает мьютекс. Затем он продолжает вызывать рабочую функцию, используя собственную локальную копию аргументов.
Подобный процесс происходит, когда вы пытаетесь остановить все это. «продюсер» блокирует мьютекс, устанавливает m_Run
в false
и уведомляет всех. «потребительская» нить проснулась, уведомляет, что m_Run
ложно и завершает цикл. Обратите внимание, что это не нарушит рабочую функцию, которая уже выполняется - вам нужно подождать (обратите внимание на вызов join
в деструкторе), чтобы она завершилась.