2017-10-16 21 views
1

我想編寫一個類似於ppltasks的簡單任務類。C++ 11/11如何使用返回類型爲void或其他的函數創建異步任務

我希望我的任務類可以創建任務如下:

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

但我想在任務運行功能可能有不同的類型:無效,浮點數,字符串等。而這裏的關鍵點在於:

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

我怎麼能處理無效的功能和其他類型的功能使用相同的CreateTask?

以下是我的任務類的全碼:

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; 
} 
+0

通過添加噸和噸的標籤調度代碼。在C++ 17中,由於「如果constexpr」,它變得更容易一些。但這是一個[已知問題](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0146r1.html),沒有很好的解決方案。 – nwp

+0

@nwp噸和噸? – Yakk

+0

@Yakk是的。在實踐中,你將有多個功能和三重冗餘。你很難在你看到的所有樣板中看到代碼。嘗試不支持'void'返回類型以便能夠在之後維護代碼是有意義的。 – nwp

回答

2

讓通過關注點分離攻擊這一點。

首先,一個可調用的輸出pipeing到另一個問題:

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)); 
} 

在這裏,我們仔細允許調度員(我們如何讓未來的)如果需要傳遞到Task.then'延續將使用調度員創建鏈式期貨。

Live example

作爲獎勵,您現在擁有一個簡單的流式操作鏈操作庫。

請注意,我用async爲基礎的實現替換了基於thread的實現,因爲系統在程序完成之前未能正確等待線程終止,從而導致未定義的行爲。您仍然可以將Dispatcher替換爲使用線程的那個。


Regular Void提案試圖擺脫這個問題。我不知道它的工作有多好。


以上不MSVC的當前版本構建,因爲MSVC不能是一個正確的編譯器,它的異步使用包裝任務,其包裝任務保存其任務一個std功能,這導致async錯誤地要求被可複製調用的任務爲std::function type-erases複製。

這打破了上面的代碼,因爲我們在我們的異步任務中存儲了一個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

+0

一個不錯的解決方案恕我直言:)。只是一個小小的警告,我碰到自己嘗試一下:如果你想讓它「異步」運行,我發現你必須保存'CreateTask'('auto task = CreateTask(...)。然後(。 ..);')。否則,'std :: future'的析構函數會阻塞,因爲我們正在使用'std :: async'。然而,這不是什麼大不了的事。 – Banan

+0

@Yakk現場示例無法在visual studio中編譯,也無法在xcode中編譯。 – Yonghui

+0

@永輝修復了打破叮咚的問題(並且應該打破)。 Visual Studio比較難,因爲它不是一個真正的[tag:C++ 14]編譯器。 – Yakk

相關問題