gpt4 book ai didi

C++11/14 如何使用返回类型为 void 或其他类型的函数创建异步任务

转载 作者:行者123 更新时间:2023-11-30 02:22:54 25 4
gpt4 key购买 nike

我想写一个类似于 ppltasks 的简单任务类。

我希望我的任务类可以创建如下任务:

1.CreateTask([=]{}).then([=]{...});
2.CreateTask([=]{ return X; }).then([=](UnknownType X){...});

但我想在任务中运行的函数可能有不同的类型:void、float、string 等。这里是关键点:

auto val = func();    // If func is a void function
prms->set_value(val); // here should be: func(); prms->set_value();

如何使用相同的 CreateTask 处理 void 函数和其他类型的函数?

以下是我的任务类的完整代码:

template<typename TType>
class Task
{
public:
Task(){};
std::future<TType> mTask;
template<typename F>
auto then(F func)->Task<decltype(func(mTask.get()))>
{
Task<decltype(func(mTask.get()))> task;
auto prms = make_shared<promise<decltype(func(mTask.get()))>>();
task.mTask = prms->get_future();
thread th([=]
{
auto val = func(mTask.get()); //
prms->set_value(val);
});
th.detach();
return task;
});
};

inline void CreateTask(F func) -> Task<decltype(func())>
{
Task<decltype(func())> task;
auto prms = make_shared<promise<decltype(func())>>();
task.mTask = prms->get_future();
thread th([=]
{
auto val = func();
prms->set_value(val);
});
th.detach();
return task;
}

最佳答案

让我们通过关注点分离来解决这个问题。

首先,将一个可调用对象的输出通过管道传输到另一个可调用对象的问题:

template<class Index>
auto get_nth( Index ) {
return [](auto&&...args)noexcept(true)->decltype(auto) {
return std::get<Index::value>(
std::forward_as_tuple( decltype(args)(args)... )
);
};
}

template<class F>
struct pipeable_t;

template<class F>
pipeable_t<std::decay_t<F>> make_pipe(F&&);

struct is_pipeable {
template<
class Lhs, class Rhs,
std::enable_if_t<
std::is_base_of<is_pipeable, std::decay_t<Lhs>>{}
|| std::is_base_of<is_pipeable, std::decay_t<Rhs>>{}
, int> = 0
>
friend
auto operator|( Lhs&& lhs, Rhs&& rhs ) {
auto pipe_result=
[lhs = std::forward<Lhs>(lhs), rhs=std::forward<Rhs>(rhs)]
(auto&&...args)mutable ->decltype(auto)
{
auto value_is_void = std::is_same< decltype( lhs(decltype(args)(args)...) ), void >{};
auto pipe_chooser = get_nth( value_is_void );
auto pipe_execution = pipe_chooser(
[&](auto&& lhs, auto&& rhs)->decltype(auto){ return rhs( lhs( decltype(args)(args)... ) ); },
[&](auto&& lhs, auto&& rhs)->decltype(auto){ lhs( decltype(args)(args)... ); return rhs(); }
);
return pipe_execution( lhs, rhs );
};
return make_pipe( std::move(pipe_result) );
}
};

template<class F>
struct pipeable_t:is_pipeable {
F f;
pipeable_t( F fin ):f(std::forward<F>(fin)) {}
template<class...Args>
auto operator()(Args&&...args) const {
return f( std::forward<Args>(args)... );
}
template<class...Args>
auto operator()(Args&&...args) {
return f( std::forward<Args>(args)... );
}
};

template<class F>
pipeable_t<std::decay_t<F>> make_pipe(F&& f) { return {std::forward<F>(f)}; }

template<class T>
auto future_to_factory( std::future<T>&& f ) {
return [f=std::move(f)]() mutable { return f.get(); };
}

template<class T>
auto make_pipe( std::future<T>&& f ) {
return make_pipe( future_to_factory( std::move(f) ) );
}

我们可以make_pipe( some lambda ) 现在它可以被送入更多的东西。

管道本身是可执行的和可管道化的。我们可以让任何可执行的东西成为管道,我们可以让 std::future 成为管道。

一旦有了这个,我们就根据它重写您的Task:

struct dispatch_via_async {
template<class F>
auto operator()(F&& f)const {
return std::async( std::launch::async, std::forward<F>(f) );
}
};

template<class T, class Dispatch=dispatch_via_async>
class Task;

template<class F, class D=dispatch_via_async>
using task_type = Task<std::decay_t<std::result_of_t<F&&()>>, D>;

template<class F, class D=dispatch_via_async>
task_type<F, D> CreateTask(F&& func, D&& d={});

template<class T, class Dispatch>
class Task :
public is_pipeable, // why not?
private Dispatch
{
std::future<T> mTask;

Dispatch& my_dispatch() { return *this; }
public:
T operator()() { return mTask.get(); }
Task(){};
template<class F>
auto then(F&& func)&&
{
return CreateTask(
make_pipe(std::move(mTask)) | std::forward<F>(func),
std::move(my_dispatch())
);
}

template<class F, class D=Dispatch>
explicit Task( F&& func, D&& dispatch={} ):
Dispatch(std::forward<D>(dispatch))
{
mTask = my_dispatch()(
[func = std::forward<F>(func)]() mutable
-> decltype(func())
{
return func();
}
);
}
};

template<class F, class D>
task_type<F,D> CreateTask(F&& func, D&& d)
{
return task_type<F,D>( std::forward<F>(func), std::forward<D>(d) );
}

在这里,如果需要,我们小心地允许将调度程序(我们如何获得 future )传递到 Task 中。 .then 的延续将使用调度程序创建链接的 future 。

Live example .

作为奖励,您现在拥有一个简单的流式操作链库。

请注意,我将基于 thread 的实现替换为基于 async 的实现,因为您的系统无法在程序完成之前正确等待线程终止,这会导致 undefined行为。您仍然可以将 Dispatcher 替换为使用线程的 Dispatcher。


, Regular Void提案试图摆脱这个问题。我不知道它的工作效果如何。


上面没有构建在当前版本的 MSVC 中,因为 MSVC 不是一个合适的 编译器,因为它的异步使用打包任务,它的打包任务将其任务存储在一个标准函数中,这导致异步错误地要求任务被 copyable 调用为 std::function 键入删除拷贝。

这打破了上面的代码,因为我们在我们的异步任务中存储了一个 std::future,它不能被复制。

我们可以以适度的成本解决这个问题。最简单、最孤立的方法是将 future_to_factory 更改为:

template<class T>
auto future_to_factory( std::future<T>&& f ) {
return [f=std::make_shared<std::future<T>>(std::move(f))]() mutable { return f->get(); };
}

和代码 compiles on visual studio .

关于C++11/14 如何使用返回类型为 void 或其他类型的函数创建异步任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46772430/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com