【链接】:tasks:https://github.com/mhogomchungu/tasks
Qt / C ++中使用任务和延续进行异步编程。项目旨在使用现代C ++在Qt / C ++中进行基于异步的编程
#ifndef __TASK_H_INCLUDED__
#define __TASK_H_INCLUDED__
#include <type_traits>
#include <vector>
#include <utility>
#include <future>
#include <functional>
#include <QThread>
#include <QEventLoop>
#include <QMutex>
#include <QProcess>
#include <QVariant>
namespace Task
{
template< typename T >
class future;
namespace detail
{
template< typename T >
void add_void(Task::future< T >&, Task::future< T >&, std::function< T() >&&);
template< typename T >
void add(Task::future< T >&, Task::future< T >&, std::function< void(T) >&&);
template<typename Function>
class functionWrapper
{
public:
template< typename ... Args >
auto operator()(Args&& ... args) const
{
return (*m_function)(std::forward<Args>(args) ...);
}
functionWrapper(Function function) :
m_function(std::make_shared<Function>(Function(std::move(function))))
{
}
private:
std::shared_ptr<Function> m_function;
};
template<typename Function>
functionWrapper<Function> function(Function function)
{
return functionWrapper<Function>(std::move(function));
}
#if __cplusplus >= 201703L
template<typename Function, typename ... Args>
using result_of = std::invoke_result_t<Function, Args ...>;
#else
template<typename Function, typename ... Args>
using result_of = std::result_of_t<Function(Args ...)>;
#endif
template<typename Function>
using copyable = std::enable_if_t<std::is_copy_constructible<Function>::value, int>;
template<typename Function>
using not_copyable = std::enable_if_t<!std::is_copy_constructible<Function>::value, int>;
template<typename ReturnType, typename Function, typename ... Args>
using has_same_return_type = std::enable_if_t<std::is_same<result_of<Function, Args...>, ReturnType>::value, int>;
template<typename Function, typename ... Args>
using has_void_return_type = has_same_return_type<void, Function, Args...>;
template<typename Function, typename ... Args>
using has_bool_return_type = has_same_return_type<bool, Function, Args...>;
template<typename Function, typename ... Args>
using has_non_void_return_type = std::enable_if_t<!std::is_void<result_of<Function, Args...>>::value, int>;
template<typename Function, typename ... Args>
using has_argument = has_same_return_type<result_of<Function, Args...>, Function, Args...>;
template<typename Function>
using has_no_argument = has_same_return_type<result_of<Function>, Function>;
template<typename Function, typename ... Args>
using returns_void = has_void_return_type<Function, Args...>;
template<typename Function, typename ... Args>
using returns_value = has_non_void_return_type<Function, Args...>;
}
template< typename T >
struct pair {
pair(std::function< T() > first, std::function< void(T) > second) :
value(std::make_pair(std::move(first), std::move(second)))
{
}
std::pair< std::function< T() >, std::function< void(T) > > value;
};
template<>
struct pair<void> {
pair(std::function< void() > first, std::function< void() > second) :
value(std::make_pair(std::move(first), std::move(second)))
{
}
std::pair< std::function< void() >, std::function< void() > > value;
};
template< typename E,
typename F,
Task::detail::not_copyable<E> = 0,
Task::detail::not_copyable<F> = 0 >
pair<Task::detail::result_of<E>> make_pair(E e, F f)
{
using type = Task::detail::result_of<E>;
return pair<type>(Task::detail::function(std::move(e)), Task::detail::function(std::move(f)));
}
template< typename E,
typename F,
Task::detail::copyable<E> = 0,
Task::detail::copyable<F> = 0 >
pair<Task::detail::result_of<E>> make_pair(E e, F f)
{
return pair<Task::detail::result_of<E>>(std::move(e), std::move(f));
}
template< typename E,
typename F,
Task::detail::not_copyable<E> = 0,
Task::detail::copyable<F> = 0 >
pair<Task::detail::result_of<E>> make_pair(E e, F f)
{
using type = Task::detail::result_of<E>;
return pair<type>(Task::detail::function(std::move(e)), std::move(f));
}
template< typename E,
typename F,
Task::detail::copyable<E> = 0,
Task::detail::not_copyable<F> = 0 >
pair<Task::detail::result_of<E>> make_pair(E e, F f)
{
using type = Task::detail::result_of<E>;
return pair<type>(std::move(e), Task::detail::function(std::move(f)));
}
template< typename T >
class future : private QObject
{
public:
/*
* Use this API if you care about the result
*/
//std::function< void( T ) >
template<typename Function,
Task::detail::has_argument<Function, T> = 0,
Task::detail::copyable<Function> = 0>
void then(Function function)
{
m_function = std::move(function);
this->start();
}
template<typename Function,
Task::detail::has_argument<Function, T> = 0,
Task::detail::not_copyable<Function> = 0>
void then(Function function)
{
m_function = Task::detail::function(std::move(function));
this->start();
}
/*
* Use this API if you DO NOT care about the result
*/
//std::function< void( void ) >
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::copyable<Function> = 0>
void then(Function function)
{
m_function_1 = std::move(function);
this->start();
}
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::not_copyable<Function> = 0>
void then(Function function)
{
m_function_1 = Task::detail::function(std::move(function));
this->start();
}
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::not_copyable<Function> = 0>
void queue(Function function)
{
if (this->manages_multiple_futures()) {
m_function_1 = Task::detail::function(std::move(function));
this->_queue();
}
else {
this->then(Task::detail::function(std::move(function)));
}
}
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::copyable<Function> = 0>
void queue(Function function)
{
if (this->manages_multiple_futures()) {
m_function_1 = std::move(function);
this->_queue();
}
else {
this->then(std::move(function));
}
}
void queue()
{
if (this->manages_multiple_futures()) {
m_function_1 = []() {};
this->_queue();
}
else {
this->then([]() {});
}
}
/*
* Below two API just exposes existing functionality using more standard names
*/
template<typename Function>
void when_all(Function function)
{
this->then(std::move(function));
}
template<typename Function>
void when_seq(Function function)
{
this->queue(std::move(function));
}
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::copyable<Function> = 0>
void when_any(Function function)
{
if (this->manages_multiple_futures()) {
this->_when_any(std::move(function));
}
else {
this->then(std::move(function));
}
}
template<typename Function,
Task::detail::has_no_argument<Function> = 0,
Task::detail::not_copyable<Function> = 0>
void when_any(Function function)
{
if (this->manages_multiple_futures()) {
this->_when_any(Task::detail::function(std::move(function)));
}
else {
this->then(Task::detail::function(std::move(function)));
}
}
void when_all()
{
this->then([]() {});
}
void when_seq()
{
this->queue([]() {});
}
void when_any()
{
if (this->manages_multiple_futures()) {
this->_when_any([]() {});
}
else {
this->then([]() {});
}
}
T get()
{
if (this->manages_multiple_futures()) {
for (auto& it : m_tasks) {
it.second(it.first->get());
}
this->deleteLater();
return T();
}
else {
return m_get();
}
}
T await()
{
QEventLoop p;
T q;
m_function = [&](T&& r) { q = std::move(r); p.exit(); };
this->start();
p.exec();
return q;
}
bool manages_multiple_futures()
{
return m_tasks.size() > 0;
}
const std::vector< QThread * >& all_threads()
{
return m_threads;
}
QThread * first_thread()
{
return m_threads[0];
}
QThread * thread_at(std::vector< QThread * >::size_type s)
{
return m_threads[s];
}
void start()
{
if (this->manages_multiple_futures()) {
this->_start();
}
else {
m_start();
}
}
void cancel()
{
if (this->manages_multiple_futures()) {
for (auto& it : m_tasks) {
it.first->cancel();
}
this->deleteLater();
}
else {
m_cancel();
}
}
future() = default;
future(const future&) = delete;
future(future&&) = delete;
future& operator=(const future&) = delete;
future& operator=(future&&) = delete;
future(QThread * e,
std::function< void() >&& start,
std::function< void() >&& cancel,
std::function< T() >&& get) :
m_thread(e),
m_start(std::move(start)),
m_cancel(std::move(cancel)),
m_get(std::move(get))
{
if (m_thread) {
m_threads.push_back(m_thread);
}
else {
/*
* This object was created by "_private_future< T >()" class.
* It has no QThread of its own because it only manages other futures.
*
*/
}
}
void run(T&& r)
{
if (m_function_1 != nullptr) {
m_function_1();
}
else if (m_function != nullptr) {
m_function(std::move(r));
}
}
template< typename E >
friend void Task::detail::add(Task::future< E >&,
Task::future< E >&,
std::function< void(E) >&&);
private:
void _when_any(std::function< void() > function)
{
m_when_any_function = std::move(function);
for (auto& it : m_tasks) {
it.first->then([&](T&& e) {
QMutexLocker m(&m_mutex);
m_counter++;
if (m_task_not_run) {
m_task_not_run = false;
m.unlock();
it.second(std::forward<T>(e));
m_when_any_function();
}
else {
m.unlock();
it.second(std::forward<T>(e));
}
if (m_counter == m_tasks.size()) {
this->deleteLater();
}
});
}
}
void _queue()
{
m_tasks[m_counter].first->then([this](T&& e) {
m_tasks[m_counter].second(std::forward<T>(e));
m_counter++;
if (m_counter == m_tasks.size()) {
m_function_1();
this->deleteLater();
}
else {
this->_queue();
}
});
}
void _start()
{
for (auto& it : m_tasks) {
it.first->then([&](T&& e) {
QMutexLocker m(&m_mutex);
Q_UNUSED(m);
m_counter++;
it.second(std::forward<T>(e));
if (m_counter == m_tasks.size()) {
if (m_function_1 != nullptr) {
m_function_1();
}
else if (m_function != nullptr) {
m_function(T());
}
this->deleteLater();
}
});
}
}
QThread * m_thread = nullptr;
std::function< void(T) > m_function = nullptr;
std::function< void() > m_function_1 = nullptr;
std::function< void() > m_start = []() {};
std::function< void() > m_cancel = []() {};
std::function< T() > m_get = []() { return T(); };
std::function< void() > m_when_any_function;
QMutex m_mutex;
std::vector< std::pair< Task::future< T > *, std::function< void(T) > > > m_tasks;
std::vector< QThread * > m_threads;
decltype(m_tasks.size()) m_counter = 0;
bool m_task_not_run = true;
};
template<>
class future< void > : private QObject
{
public:
template<typename Function, Task::detail::copyable<Function> = 0>
void then(Function function)
{
m_function = std::move(function);
this->start();
}
template<typename Function, Task::detail::not_copyable<Function> = 0>
void then(Function function)
{
m_function = Task::detail::function(std::move(function));
this->start();
}
template<typename Function, Task::detail::copyable<Function> = 0>
void queue(Function function)
{
if (this->manages_multiple_futures()) {
m_function = std::move(function);
this->_queue();
}
else {
this->then(std::move(function));
}
}
template<typename Function, Task::detail::not_copyable<Function> = 0>
void queue(Function function)
{
if (this->manages_multiple_futures()) {
m_function = Task::detail::function(std::move(function));
this->_queue();
}
else {
this->then(Task::detail::function(std::move(function)));
}
}
void queue()
{
if (this->manages_multiple_futures()) {
m_function = []() {};
this->_queue();
}
else {
this->then([]() {});
}
}
void when_any()
{
if (this->manages_multiple_futures()) {
this->_when_any([]() {});
}
else {
this->then([]() {});
}
}
template<typename Function, Task::detail::copyable<Function> = 0>
void when_any(Function function)
{
if (this->manages_multiple_futures()) {
this->_when_any(std::move(function));
}
else {
this->then(std::move(function));
}
}
template<typename Function, Task::detail::not_copyable<Function> = 0>
void when_any(Function function)
{
if (this->manages_multiple_futures()) {
this->_when_any(Task::detail::function(std::move(function)));
}
else {
this->then(Task::detail::function(std::move(function)));
}
}
/*
* Below two API just exposes existing functionality using more standard names
*/
template<typename Function>
void when_all(Function function)
{
this->then(std::move(function));
}
template<typename Function>
void when_seq(Function function)
{
this->queue(std::move(function));
}
void when_all()
{
this->then([]() {});
}
void when_seq()
{
this->queue();
}
void get()
{
if (this->manages_multiple_futures()) {
for (auto& it : m_tasks) {
it.first->get();
it.second();
}
this->deleteLater();
}
else {
m_get();
}
}
void await()
{
QEventLoop p;
m_function = [&]() { p.exit(); };
this->start();
p.exec();
}
bool manages_multiple_futures()
{
return m_tasks.size() > 0;
}
const std::vector< QThread * >& all_threads()
{
return m_threads;
}
QThread * first_thread()
{
return m_threads[0];
}
QThread * thread_at(std::vector< QThread * >::size_type s)
{
return m_threads[s];
}
void start()
{
if (this->manages_multiple_futures()) {
this->_start();
}
else {
m_start();
}
}
void cancel()
{
if (this->manages_multiple_futures()) {
for (auto& it : m_tasks) {
it.first->cancel();
}
this->deleteLater();
}
else {
m_cancel();
}
}
future() = default;
future(const future&) = delete;
future(future&&) = delete;
future& operator=(const future&) = delete;
future& operator=(future&&) = delete;
future(QThread * e,
std::function< void() >&& start,
std::function< void() >&& cancel,
std::function< void() >&& get) :
m_thread(e),
m_start(std::move(start)),
m_cancel(std::move(cancel)),
m_get(std::move(get))
{
if (m_thread) {
m_threads.push_back(m_thread);
}
else {
/*
* This object was created by "_private_future< T >()" class.
* It has no QThread of its own because it only manages other futures.
*
*/
}
}
template< typename T >
friend void Task::detail::add_void(Task::future< T >&,
Task::future< T >&,
std::function< T() >&&);
void run()
{
m_function();
}
private:
void _when_any(std::function< void() > function)
{
m_when_any_function = std::move(function);
for (auto& it : m_tasks) {
it.first->then([&]() {
QMutexLocker m(&m_mutex);
m_counter++;
if (m_task_not_run) {
m_task_not_run = false;
m.unlock();
it.second();
m_when_any_function();
}
else {
m.unlock();
it.second();
}
if (m_counter == m_tasks.size()) {
this->deleteLater();
}
});
}
}
void _queue()
{
m_tasks[m_counter].first->then([this]() {
m_tasks[m_counter].second();
m_counter++;
if (m_counter == m_tasks.size()) {
m_function();
this->deleteLater();
}
else {
this->_queue();
}
});
}
void _start()
{
for (auto& it : m_tasks) {
it.first->then([&]() {
QMutexLocker m(&m_mutex);
Q_UNUSED(m);
m_counter++;
it.second();
if (m_counter == m_tasks.size()) {
m_function();
this->deleteLater();
}
});
}
}
QThread * m_thread = nullptr;
std::function< void() > m_function = []() {};
std::function< void() > m_start = []() {};
std::function< void() > m_cancel = []() {};
std::function< void() > m_get = []() {};
std::function< void() > m_when_any_function;
QMutex m_mutex;
std::vector< std::pair< Task::future< void > *, std::function< void() > > > m_tasks;
std::vector< QThread * > m_threads;
decltype(m_tasks.size()) m_counter = 0;
bool m_task_not_run = true;
};
namespace detail
{
/*
* -------------------------Start of internal helper functions-------------------------
*/
template< typename Type, typename Function >
class ThreadHelper : public QThread
{
public:
ThreadHelper(Function function) :
m_function(std::move(function)),
m_future(this,
[this]() { this->start(); },
[this]() { this->deleteLater(); },
[this]() { this->deleteLater(); return m_function(); })
{
connect(this, &QThread::finished, this, &QThread::deleteLater);
}
Task::future<Type>& Future()
{
return m_future;
}
private:
~ThreadHelper()
{
m_future.run(std::move(m_result));
}
void run()
{
m_result = m_function();
}
Function m_function;
Task::future<Type> m_future;
Type m_result;
};
template< typename Function>
class ThreadHelperVoid : public QThread
{
public:
ThreadHelperVoid(Function function) :
m_function(std::move(function)),
m_future(this,
[this]() { this->start(); },
[this]() { this->deleteLater(); },
[this]() { m_function(); this->deleteLater(); })
{
connect(this, &QThread::finished, this, &QThread::deleteLater);
}
Task::future< void >& Future()
{
return m_future;
}
private:
~ThreadHelperVoid()
{
m_future.run();
}
void run()
{
m_function();
}
Function m_function;
Task::future< void > m_future;
};
template<typename Fn, Task::detail::returns_value<Fn> = 0>
Task::future<Task::detail::result_of<Fn>>& run(Fn function)
{
using t = Task::detail::result_of<Fn>;
return (new ThreadHelper<t, Fn>(std::move(function)))->Future();
}
template<typename Fn, Task::detail::returns_void<Fn> = 0>
Task::future<Task::detail::result_of<Fn>>& run(Fn function)
{
return (new ThreadHelperVoid<Fn>(std::move(function)))->Future();
}
template< typename T >
void add(Task::future< T >& a, Task::future< T >& b, std::function< void(T) >&& c)
{
a.m_tasks.emplace_back(std::addressof(b), std::move(c));
a.m_threads.push_back(b.m_thread);
}
template< typename T >
void add_void(Task::future< T >& a, Task::future< T >& b, std::function< T() >&& c)
{
a.m_tasks.emplace_back(std::addressof(b), std::move(c));
a.m_threads.push_back(b.m_thread);
}
template< typename T >
void add_task(Task::future< T >& f)
{
Q_UNUSED(f)
}
template< typename T >
void add_future(Task::future< T >& f)
{
Q_UNUSED(f)
}
template< typename T >
void add_pair(Task::future< T >& f)
{
Q_UNUSED(f)
}
template< typename T >
void add_pair_void(Task::future< T >& f)
{
Q_UNUSED(f)
}
template< typename ... T,
typename Function,
Task::detail::not_copyable<Function> = 0 >
void add_task(Task::future< void >& f, Function e, T&& ... t);
template< typename ... T,
typename Function,
Task::detail::copyable<Function> = 0 >
void add_task(Task::future< void >& f, Function e, T&& ... t)
{
add_void(f, Task::detail::run(std::function< void() >(std::move(e))),
std::function< void() >([]() {}));
add_task(f, std::forward<T>(t) ...);
}
template< typename ... T,
typename Function,
Task::detail::not_copyable<Function> >
void add_task(Task::future< void >& f, Function e, T&& ... t)
{
auto a = std::function< void() >(Task::detail::function(std::move(e)));
add_void(f, Task::detail::run(std::move(a)),
std::function< void() >([]() {}));
add_task(f, std::forward<T>(t) ...);
}
template< typename ... T >
void add_future(Task::future< void >& f, Task::future< void >& e, T&& ... t)
{
add_void(f, e, std::function< void() >([]() {}));
add_future(f, std::forward<T>(t) ...);
}
template< typename E, typename F, typename ... T >
void add_pair(Task::future< E >& f, F&& s, T&& ... t)
{
add(f, Task::detail::run(std::move(s.value.first)), std::move(s.value.second));
add_pair(f, std::forward<T>(t) ...);
}
template< typename F, typename ... T >
void add_pair_void(Task::future< void >& f, F&& s, T&& ... t)
{
add_void(f, Task::detail::run(std::move(s.value.first)), std::move(s.value.second));
add_pair_void(f, std::forward<T>(t) ...);
}
template< typename T >
Task::future< T >& future()
{
return *(new Task::future< T >());
}
} //end of detail namespace
/*
* -------------------------End of internal helper functions-------------------------
*/
template< typename Fn, Task::detail::copyable<Fn> = 0 >
auto& run(Fn function)
{
return Task::detail::run(std::move(function));
}
template< typename Fn, Task::detail::not_copyable<Fn> = 0 >
auto& run(Fn function)
{
return Task::detail::run(Task::detail::function(std::move(function)));
}
#if __cplusplus > 201703L
template< typename Fn, typename ... Args >
future<std::invoke_result_t<Fn, Args...>>& run(Fn function, Args ... args)
{
return Task::run([function = std::move(function), ... args = std::move(args)]()mutable{
return function(std::move(args) ...);
});
}
#elif __cplusplus == 201703L
template< typename Fn, typename ... Args >
future<std::invoke_result_t<Fn, Args...>>& run(Fn function, Args ... args)
{
return Task::run([args = std::make_tuple(std::move(args) ...), function = std::move(function)]()mutable{
return std::apply([function = std::move(function)](Args ... args){
return function(std::move(args) ...);
}, std::move(args));
});
}
#else
template< typename Fn, typename ... Args >
future<std::result_of_t<Fn(Args...)>>& run(Fn function, Args ... args)
{
return Task::run([=, function = std::move(function)](){
return function(std::move(args) ...);
});
}
#endif
class progress : public QObject {
Q_OBJECT
public:
template< typename function >
progress(QObject * obj, function fn)
{
connect(this, &progress::update, obj, std::move(fn));
}
signals:
void update(QVariant x) const;
private:
};
template< typename Fn, typename cb >
future<Task::detail::result_of<Fn, const progress&>>& run(QObject * obj, Fn function, cb rp)
{
return Task::run([obj, rp = std::move(rp), function = std::move(function)](){
return function(progress(obj, std::move(rp)));
});
}
template< typename Function,
Task::detail::copyable<Function> = 0,
typename ... T >
Task::future< void >& run_tasks(Function f, T ... t)
{
auto& e = Task::detail::future< void >();
Task::detail::add_task(e, std::move(f), std::move(t) ...);
return e;
}
template< typename Function,
Task::detail::not_copyable<Function> = 0,
typename ... T >
Task::future< void >& run_tasks(Function f, T ... t)
{
auto& e = Task::detail::future< void >();
Task::detail::add_task(e, Task::detail::function(std::move(f)), std::move(t) ...);
return e;
}
template< typename ... T >
Task::future< void >& run_tasks(Task::future< void >& s, T&& ... t)
{
auto& e = Task::detail::future< void >();
Task::detail::add_future(e, s, std::forward<T>(t) ...);
return e;
}
template< typename ... T >
Task::future< void >& run(pair< void > s, T ... t)
{
auto& e = Task::detail::future< void >();
Task::detail::add_pair_void(e, std::move(s), std::move(t) ...);
return e;
}
template< typename E, typename ... T >
Task::future< E >& run(pair< E > s, T ... t)
{
auto& e = Task::detail::future< E >();
Task::detail::add_pair(e, std::move(s), std::move(t) ...);
return e;
}
/*
*
* A few useful helper functions
*
*/
template< typename Fn >
Task::detail::result_of<Fn> await(Fn function)
{
return Task::run(std::move(function)).await();
}
template< typename Fn, typename ... Args >
Task::detail::result_of<Fn, Args ...> await(Fn function, Args ... args)
{
return Task::run(std::move(function), std::move(args) ...).await();
}
template< typename T >
T await(Task::future<T>& e)
{
return e.await();
}
template< typename T >
T await(std::future<T> t)
{
return Task::await<T>([&]() { return t.get(); });
}
/*
* These methods run their arguments in a separate thread and does not offer
* continuation feature.Useful when wanting to just run a function in a
* different thread.
*/
template< typename Fn >
void exec(Fn function)
{
Task::run(std::move(function)).start();
}
template< typename Fn, typename ... Args >
void exec(Fn function, Args ... args)
{
Task::run(std::move(function), std::move(args) ...).start();
}
template< typename T >
void exec(Task::future<T>& e)
{
e.start();
}
namespace process {
class result {
public:
result() = default;
result(int exit_code) :
m_finished(true),
m_exitCode(exit_code),
m_exitStatus(0)
{
}
template< typename E, typename F >
result(E&& std_out,
F&& std_error,
int exit_code,
int exit_status,
bool finished) :
m_stdOut(std::forward<E>(std_out)),
m_stdError(std::forward<F>(std_error)),
m_finished(finished),
m_exitCode(exit_code),
m_exitStatus(exit_status)
{
}
result(QProcess& e, int s)
{
m_finished = e.waitForFinished(s);
m_stdOut = e.readAllStandardOutput();
m_stdError = e.readAllStandardError();
m_exitCode = e.exitCode();
m_exitStatus = e.exitStatus();
}
const QByteArray& std_out() const
{
return m_stdOut;
}
const QByteArray& std_error() const
{
return m_stdError;
}
bool finished() const
{
return m_finished;
}
bool success() const
{
return m_exitCode == 0 &&
m_exitStatus == QProcess::NormalExit &&
m_finished == true;
}
bool failed() const
{
return !this->success();
}
int exit_code() const
{
return m_exitCode;
}
int exit_status() const
{
return m_exitStatus;
}
private:
QByteArray m_stdOut;
QByteArray m_stdError;
bool m_finished = false;
int m_exitCode = 255;
int m_exitStatus = 255;
};
static inline Task::future< result >& run(const QString& cmd,
const QStringList& args,
int waitTime = -1,
const QByteArray& password = QByteArray(),
const QProcessEnvironment& env = QProcessEnvironment(),
std::function< void() > setUp_child_process = []() {})
{
return Task::run([=]() {
class Process : public QProcess {
public:
Process(std::function< void() > function,
const QProcessEnvironment& env) :
m_function(std::move(function))
{
this->setProcessEnvironment(env);
}
protected:
void setupChildProcess()
{
m_function();
}
private:
std::function< void() > m_function;
} exe(std::move(setUp_child_process), env);
if (args.isEmpty()) {
#if QT_VERSION < QT_VERSION_CHECK( 5,15,0 )
exe.start(cmd);
#else
exe.start(cmd, args);
#endif
}
else {
exe.start(cmd, args);
}
if (!password.isEmpty()) {
exe.waitForStarted(waitTime);
exe.write(password);
exe.closeWriteChannel();
}
return result(exe, waitTime);
});
}
static inline Task::future< result >& run(const QString& cmd, const QByteArray& password)
{
return Task::process::run(cmd, {}, -1, password);
}
static inline Task::future< result >& run(const QString& cmd,
const QStringList& args,
const QByteArray& password)
{
return Task::process::run(cmd, args, -1, password);
}
}
}
这是一个基于Qt的异步任务处理库,提供了类似C++标准库std::future
的功能,但更加灵活且与Qt框架深度集成。
核心设计
1.1 主要特性
-
支持异步任务执行
-
提供多种任务执行模式:并行、串行、任意完成等
-
与Qt事件循环集成,支持GUI线程不阻塞
-
支持任务链式调用
-
提供进程执行封装
1.2 核心类
-
Task::future<T>
: 模板类,表示异步操作的结果 -
Task::pair<T>
: 辅助结构,用于组合任务函数和回调函数 -
Task::progress
: 进度报告类 -
Task::process::result
: 进程执行结果类
2. 关键实现细节
2.1 future类实现
future<T>
是核心类,有两种特化版本:
-
future<T>
: 处理有返回值的任务 -
future<void>
: 处理无返回值的任务
主要方法:
-
then()
: 添加完成回调 -
await()
: 同步等待结果(不阻塞GUI) -
get()
: 同步获取结果(可能阻塞) -
queue()
: 串行执行任务 -
when_all()
/when_any()
: 并行执行控制 -
start()
: 启动任务 -
cancel()
: 取消任务
内部机制:
-
使用QThread在后台执行任务
-
通过QEventLoop实现非阻塞等待
-
使用QMutex进行线程同步
2.2 任务执行流程
-
用户通过
Task::run()
创建任务 -
库内部创建
ThreadHelper
或ThreadHelperVoid
(继承自QThread) -
线程完成后通过Qt信号槽机制通知主线程
-
主线程执行回调函数
2.3 模板元编程技巧
库中大量使用了SFINAE和模板元编程来实现函数签名的检查和适配:
template<typename Function>
using copyable = std::enable_if_t<std::is_copy_constructible<Function>::value, int>;
template<typename Function>
using not_copyable = std::enable_if_t<!std::is_copy_constructible<Function>::value, int>;
这些特性用于:
-
区分可复制和不可复制的函数对象
-
检查函数返回类型
-
适配不同参数数量的函数
3. 使用示例
根据接口设计,典型用法可能如下:
3.1 基本用法
// 异步执行并获取结果
auto& task = Task::run([]{
return someHeavyComputation();
});
task.then([](auto result){
// 在主线程处理结果
updateUI(result);
});
3.2 串行任务
auto& tasks = Task::run_tasks(
[]{ return task1(); },
[]{ return task2(); },
[]{ return task3(); }
);
tasks.queue([]{
// 所有任务完成后执行
});
3.3 进程执行
auto& proc = Task::process::run("git", {"pull"});
proc.then([](const Task::process::result& r){
if(r.success()) {
qDebug() << "Git pull succeeded";
}
});
功能测试
#include "task.hpp"
#include "example.h"
#include <QString>
#include <QMetaObject>
#include <QCoreApplication>
#include <iostream>
static void _testing_task_await();
static void _testing_task_future_all();
static void _testing_multiple_tasks();
static void _testing_multiple_tasks_with_start();
static void _testing_queue_with_no_results();
static void _testing_queue_with_results();
static void _testing_checking_multiple_futures();
template< typename T >
static void _print(const T& e)
{
std::cout << e << std::endl;
}
struct wait {
void task_finished(const char * s)
{
QMutexLocker m(&mutex);
counter++;
std::cout << s << std::endl;
if (counter == max) {
loop.exit();
}
}
int max = 3;
int counter = 0;
QMutex mutex;
QEventLoop loop;
};
void example::start()
{
QMetaObject::invokeMethod(this, "run", Qt::QueuedConnection);
}
QString _longRunningTask()
{
return "abc";
}
static void _printThreadID()
{
std::cout << "Thread id: " << QThread::currentThreadId() << std::endl;
}
static void _useResult(const QString& e)
{
Q_UNUSED(e)
}
/*
* A sample use where a task is run on separate thread and return a value and the returned
* value is used on another task run on the original thread
*/
static void _test_run_then()
{
std::cout << "Testing Task::run().then()" << std::endl;
/*
* print the thread id to know we are on what thread.
* We are on the original thread here.
*/
_printThreadID();
Task::run([]() {
/*
* print the thread id to know we are on what thread.
* We are on a separate thread here
*/
_printThreadID();
/*
* Do a time consuming process on a separate thread and return its result
*/
return _longRunningTask();
}).then([](QString r) {
/*
* use the returned value by the previous task
*/
_useResult(r);
/*
* print the thread id to know we are on what thread.
* We are back on the original thread and we will get here as a continuation of the task
* that completed above
*/
_printThreadID();
/*
* moving on to the next test.
*/
_testing_task_await();
});
}
/*
* Task::await() function below does the following:
* 1. suspends the "_testing_task_await" method at a point where Task::await() method is called.
* 2. creates a new thread.
* 3. runs the _longRunningTask method in the new thread.
* 4. store the result of _longRunningTask function in r.
* 5. resumes "_testing_task_await" method.
*/
static void _testing_task_await()
{
_print("Testing Task::await()");
QString e = Task::await(_longRunningTask);
_print(e.toLatin1().constData());
/*
* moving on to the next test.
*/
_testing_task_future_all();
}
/*
* Task::run() function below does the following:
* 1. Collects a bunch of tasks.
* 2. Runs each task on its own thread.
* 3. Returns a future that holds all above tasks.
* 4. .await() can be called on the future to suspend the current thread at a point
* where this medhod is called to wait for all tasks to finish.
* 5. .then() can be called on the future to register an event to be called when all
* tasks finish running.
*/
static void _testing_task_future_all()
{
auto fn1 = []() { _printThreadID(); };
auto fn2 = []() { _printThreadID(); };
auto fn3 = []() { _printThreadID(); };
_print("Testing Task::run().await() multiple tasks");
Task::future<void>& e = Task::run_tasks(fn1, fn2, fn3);
e.await();
_print("Testing Task::run().then() multiple tasks");
Task::future<void>& f1 = Task::run(fn1);
Task::future<void>& f2 = Task::run(fn2);
Task::future<void>& f3 = Task::run(fn3);
Task::future<void>& s = Task::run_tasks(f1, f2, f3);
s.then([]() {
/*
* moving on to the next test.
*/
_testing_multiple_tasks();
});
}
/*
* Task::run() function below does the following:
* 1. Collects a bunch of tasks and their continuations.
* 2. Runs each task on its own thread.
* 3. On completion of each task,run its continuation on the current thread.
* 4. Returns a future that holds all above tasks.
* 5. .await() can be called on the future to suspend the current thread at a point
* where this medhod is called to wait for all tasks and their continuations to finish.
* 6. .then() can be called on the future to register an event to be called when all
* tasks and their continuations finish.
*/
static void _testing_multiple_tasks_non_movable()
{
_print("Testing multiple tasks without continuation arguments");
auto fna1 = [a = std::unique_ptr<int>()](){ _printThreadID(); };
auto fna2 = [a = std::unique_ptr<int>()](){ _printThreadID(); };
auto fna3 = [a = std::unique_ptr<int>()](){ _printThreadID(); };
auto ra1 = [a = std::unique_ptr<int>()](){ _print("r1"); };
auto ra2 = [a = std::unique_ptr<int>()](){ _print("r2"); };
auto ra3 = [a = std::unique_ptr<int>()](){ _print("r3"); };
Task::future<void>& e = Task::run(Task::make_pair(std::move(fna1), std::move(ra1)),
Task::make_pair(std::move(fna2), std::move(ra2)),
Task::make_pair(std::move(fna3), std::move(ra3)));
e.await();
_print("Testing multiple tasks with continuation arguments");
auto fn1 = [a = std::unique_ptr<int>()](){ _printThreadID(); return 0; };
auto fn2 = [a = std::unique_ptr<int>()](){ _printThreadID(); return 0; };
auto fn3 = [a = std::unique_ptr<int>()](){ _printThreadID(); return 0; };
auto r1 = [a = std::unique_ptr<int>()](int){ _print("r1"); };
auto r2 = [a = std::unique_ptr<int>()](int){ _print("r2"); };
auto r3 = [a = std::unique_ptr<int>()](int){ _print("r3"); };
Task::future<int>& s = Task::run(Task::make_pair(std::move(fn1), std::move(r1)),
Task::make_pair(std::move(fn2), std::move(r2)),
Task::make_pair(std::move(fn3), std::move(r3)));
s.then(_testing_multiple_tasks_with_start);
}
static void _testing_multiple_tasks()
{
_print("Testing multiple tasks without continuation arguments");
auto fna1 = []() { _printThreadID(); };
auto fna2 = []() { _printThreadID(); };
auto fna3 = []() { _printThreadID(); };
auto ra1 = []() { _print("r1"); };
auto ra2 = []() { _print("r2"); };
auto ra3 = []() { _print("r3"); };
Task::future<void>& e = Task::run(Task::make_pair(fna1, ra1),
Task::make_pair(fna2, ra2),
Task::make_pair(fna3, ra3));
e.await();
_print("Testing multiple tasks with continuation arguments");
auto fn1 = []() { _printThreadID(); return 0; };
auto fn2 = []() { _printThreadID(); return 0; };
auto fn3 = []() { _printThreadID(); return 0; };
auto r1 = [](int) { _print("r1"); };
auto r2 = [](int) { _print("r2"); };
auto r3 = [](int) { _print("r3"); };
Task::future<int>& s = Task::run(Task::make_pair(fn1, r1),
Task::make_pair(fn2, r2),
Task::make_pair(fn3, r3));
s.then(_testing_multiple_tasks_non_movable);
}
static void _testing_multiple_tasks_with_start()
{
std::cout << "Testing multiple tasks with continuation arguments using start" << std::endl;
wait w;
auto fn1 = []() { _printThreadID(); return 0; };
auto fn2 = []() { _printThreadID(); return 0; };
auto fn3 = []() { _printThreadID(); return 0; };
auto r1 = [&](int) { w.task_finished("r1"); };
auto r2 = [&](int) { w.task_finished("r2"); };
auto r3 = [&](int) { w.task_finished("r3"); };
Task::future<int>& s = Task::run(Task::make_pair(fn1, r1),
Task::make_pair(fn2, r2),
Task::make_pair(fn3, r3));
s.start();
w.loop.exec();
QCoreApplication::quit();
}
static void _testing_queue_with_no_results()
{
std::cout << "Testing queue with no result" << std::endl;
auto fna1 = []() { _printThreadID(); };
auto fna2 = []() { _printThreadID(); };
auto fna3 = []() { _printThreadID(); };
auto ra1 = []() { _print("r1"); };
auto ra2 = []() { _print("r2"); };
auto ra3 = []() { _print("r3"); };
Task::future<void>& e = Task::run(Task::make_pair(fna1, ra1),
Task::make_pair(fna2, ra2),
Task::make_pair(fna3, ra3));
e.queue(_testing_queue_with_results);
}
static void _testing_queue_with_results()
{
std::cout << "Testing queue with result" << std::endl;
auto fn1 = []() { _printThreadID(); return 0; };
auto fn2 = []() { _printThreadID(); return 0; };
auto fn3 = []() { _printThreadID(); return 0; };
auto r1 = [=](int) { _print("r1"); };
auto r2 = [=](int) { _print("r2"); };
auto r3 = [=](int) { _print("r3"); };
Task::future<int>& s = Task::run(Task::make_pair(fn1, r1),
Task::make_pair(fn2, r2),
Task::make_pair(fn3, r3));
s.queue(_test_run_then);
}
static void _testing_checking_multiple_futures()
{
auto fn1 = []() {};
auto fn2 = []() {};
auto fn3 = []() {};
_print("Testing finding out if a future manages multiple futures");
Task::future<void>& e = Task::run_tasks(fn1, fn2, fn3);
const auto& z = e.all_threads();
std::string s = e.manages_multiple_futures() ? "true" : "false";
_print("A future managed multiple futures: " + s);
_print("Number of future managed: " + QString::number(z.size()).toStdString());
}
static void _test_when_any1()
{
_print("Testing when_any");
wait w;
auto ll1 = [&]() {
QThread::currentThread()->sleep(5);
w.task_finished("aaa");
};
auto ll2 = [&]() {
QThread::currentThread()->sleep(2);
w.task_finished("bbb");
};
auto ll3 = [&]() {
QThread::currentThread()->sleep(3);
w.task_finished("ccc");
};
Task::run_tasks(ll1, ll2, ll3).when_any([]() {
_print("when_any called");
});
w.loop.exec();
_print("Done testing when_any");
}
static void _test_when_any2()
{
_print("Testing when_any with result");
wait w;
auto fn1 = [&]() {
QThread::currentThread()->sleep(5);
w.task_finished("aaa");
return 0;
};
auto fn2 = [&]() {
QThread::currentThread()->sleep(2);
w.task_finished("bbb");
return 0;
};
auto fn3 = [&]() {
QThread::currentThread()->sleep(3);
w.task_finished("ccc");
return 0;
};
auto ff = [](int) {};
Task::future<int>& s = Task::run(Task::make_pair(fn1, ff),
Task::make_pair(fn2, ff),
Task::make_pair(fn3, ff));
s.when_any([]() {
_print("when_any called");
});
w.loop.exec();
_print("Done testing when_any with result");
}
static void _test_move_only_callables()
{
Task::run([a = std::unique_ptr<int>()](int x){ return x; }, 4).get();
Task::run([a = std::unique_ptr<int>()](int){}, 4).get();
Task::run([a = std::unique_ptr<int>()](){ return 6; }).get();
Task::run([a = std::unique_ptr<int>()](){}).get();
Task::await([a = std::unique_ptr<int>()](){ return 6; });
auto& tt = Task::run([a = std::unique_ptr<int>()](){ return 6; });
Task::await(tt);
Task::await(Task::run([a = std::unique_ptr<int>()](){}));
Task::await([a = std::unique_ptr<int>()](int x){ return x; }, 4);
Task::await([a = std::unique_ptr<int>()](int){}, 4);
Task::await([a = std::unique_ptr<int>()](){ return 6; });
Task::await([a = std::unique_ptr<int>()](){});
auto& zz = Task::run([a = std::unique_ptr<int>()](){ return 6; });
Task::await(zz);
Task::await(Task::run([a = std::unique_ptr<int>()](){}));
Task::exec([a = std::unique_ptr<int>()](int x){ return x; }, 4);
Task::exec([a = std::unique_ptr<int>()](int){}, 4);
Task::exec([a = std::unique_ptr<int>()](){ return 6; });
Task::exec([a = std::unique_ptr<int>()](){});
Task::run_tasks([a = std::unique_ptr<int>()](){ return 6; },
[a = std::unique_ptr<int>()](){ return 6; },
[a = std::unique_ptr<int>()](){ return 6; },
[a = std::unique_ptr<int>()](){ return 6; }).get();
Task::run_tasks([]() { return 6; },
[]() { return 6; },
[]() { return 6; },
[]() { return 6; }).get();
Task::run_tasks(Task::run([a = std::unique_ptr<int>()](){}),
Task::run([a = std::unique_ptr<int>()](){}),
Task::run([a = std::unique_ptr<int>()](){})).get();
}
static void _test_copyable_callables()
{
auto aa = [](int x) { return x; };
auto bb = []() { return 6; };
auto cc = []() {};
auto dd = [](int) {};
Task::run(aa, 4).get();
Task::run(dd, 4).get();
Task::run(bb).get();
Task::run(cc).get();
Task::await(bb);
auto& tt = Task::run(bb);
Task::await(tt);
Task::await(Task::run(cc));
Task::await(aa, 4);
Task::await(dd, 4);
Task::await(bb);
Task::await(cc);
auto& zz = Task::run(bb);
Task::await(zz);
Task::await(Task::run(cc));
Task::exec(aa, 4);
Task::exec(dd, 4);
Task::exec(bb);
Task::exec(cc);
Task::run_tasks(bb, bb, cc).get();
Task::run_tasks(Task::run(cc), Task::run(cc)).get();
}
struct foo
{
foo()
{
}
foo(int a)
{
_print(a);
}
std::unique_ptr<int> m;
};
struct www
{
void operator()()
{
}
www(const www&)
{
_print("const www&");
}
www(www&&)
{
_print("www&&");
}
www()
{
_print("www()");
}
www& operator=(const www&)
{
_print("www& operator=( const www& )");
return *this;
}
www& operator=(www&&)
{
_print("www& operator=( www&& )");
return *this;
}
std::unique_ptr<int> m;
};
void example::run()
{
#if __cplusplus >= 201703L
Task::await([](foo, foo, foo) {}, foo(7), foo(7), foo(7));
#endif
_test_copyable_callables();
_test_move_only_callables();
_test_when_any1();
_test_when_any2();
auto run_main = [](QVariant x) {
std::cout << x.value<int>() << ": mm Thread id: " << QThread::currentThreadId() << std::endl;
};
auto run_bg = [](const Task::progress& pp) {
for (int i = 0; i < 2; i++) {
std::cout << i << ": bg Thread id: " << QThread::currentThreadId() << std::endl;
pp.update(i);
QThread::currentThread()->sleep(1);
}
};
std::cout << "main Thread: " << QThread::currentThreadId() << std::endl;
Task::future<void>& abc = Task::run(this, run_bg, run_main);
abc.await();
_testing_checking_multiple_futures();
_testing_queue_with_no_results();
}
示例代码通过一系列测试函数展示了库的功能,主要测试流程如下:
-
_test_run_then()
- 测试基本任务链式调用 -
_testing_task_await()
- 测试同步等待任务完成 -
_testing_task_future_all()
- 测试多任务并行执行 -
_testing_multiple_tasks()
- 测试带回调的多任务 -
_testing_multiple_tasks_with_start()
- 测试手动启动多任务 -
_testing_queue_with_no_results()
- 测试无结果的串行队列 -
_testing_queue_with_results()
- 测试带结果的串行队列 -
_testing_checking_multiple_futures()
- 测试多future管理
2. 核心功能示例
2.1 基本任务链式调用 (_test_run_then
)
Task::run([]() { return _longRunningTask(); }).then([](QString r) { _useResult(r); });
-
run()
启动异步任务 -
then()
添加完成回调 -
任务在后台线程执行,回调在主线程执行
2.2 同步等待 (_testing_task_await
)
QString e = Task::await(_longRunningTask);
-
非阻塞等待任务完成
-
保持GUI响应
-
返回任务结果
2.3 多任务并行 (_testing_task_future_all
)
Task::future<void>& e = Task::run_tasks(fn1, fn2, fn3); e.await(); // 等待所有任务完成
-
多个任务并行执行
-
可以等待所有任务完成
2.4 带回调的多任务 (_testing_multiple_tasks
)
cpp
Task::future<int>& s = Task::run( Task::make_pair(fn1, r1), Task::make_pair(fn2, r2), Task::make_pair(fn3, r3) );
-
每个任务有自己的回调函数
-
任务完成后执行对应的回调
2.5 串行队列 (_testing_queue_with_no_results
)
Task::future<void>& e = Task::run(...); e.queue(_testing_queue_with_results);
-
任务按顺序执行
-
前一个完成后才开始下一个
3. 高级功能
3.1 "when_any" 模式
Task::run_tasks(ll1, ll2, ll3).when_any([]() { _print("when_any called"); });
-
任意一个任务完成即触发回调
-
不等待其他任务
3.2 进度报告
Task::run(this, run_bg, run_main);
-
run_bg
在后台执行 -
通过
progress
对象报告进度 -
run_main
在主线程处理进度更新
3.3 不可复制(unique)可调用对象支持
Task::run([a = std::unique_ptr<int>()](){ return 6; }).get();
-
支持移动语义的lambda
-
可以捕获unique_ptr等不可复制对象
4. 技术亮点
-
Qt集成:深度利用Qt的事件循环和线程机制,适合Qt应用程序
-
灵活的任务组合:支持多种任务执行模式(并行、串行、任意完成)
-
类型安全:通过模板确保类型安全
-
异常安全:合理使用RAII管理资源
-
现代化C++:使用C++11/14/17特性如移动语义、lambda等
-
线程安全:使用
QMutex
保护共享数据 -
线程管理:自动管理
QThread
生命周期
5. 局限性
-
依赖Qt框架,不适合非Qt项目
-
错误处理机制相对简单
-
文档和示例不足(根据代码注释)
6. 总结
-
对于简单任务,使用
run().then()
链式调用 -
需要同步结果时使用
await()
-
多个独立任务使用
run_tasks()
-
任务间有依赖时使用
queue()
-
需要进度反馈时使用
progress
对象
这个Task库提供了一个强大而灵活的异步任务处理框架,特别适合需要与Qt GUI集成的应用程序。它通过精心设计的模板类和Qt的线程机制,实现了高效、易用的异步编程模型。虽然有一些局限性,但对于Qt开发者来说,这是一个非常有价值的工具。
代码中的模板元编程和设计模式值得学习,特别是如何将标准库的future概念与Qt框架相结合的设计思路。