在c ++ 11中避免回调金字塔的最佳方法是什么?

时间:2017-03-14 03:58:54

标签: c++11

我正在学习异步编程,而且我经常发现自己编写的代码如下:

fooAsync(input, [=](Output output) {
  // ...
  asyncA(output, [=](OutputA outputA) {
    // ...
    asyncB(outputA, [=](OutputB outputB) {
      // ...
      asyncC(outputB, [=](OutputC outputC) {
        // ...
        asyncD(outputC, [=](OutputD outputD) {
          ...;
        });
      });
    });
  });
});

使用c ++ 11,有没有办法很好地组织上面的代码?非常感谢!

1 个答案:

答案 0 :(得分:3)

template<class T>
struct my_future:std::shared_future<T> {
  using std::shared_future<T>::shared_future;

  template<class F>
  auto operator->*( F f )&&
  -> my_future< std::decay_t<decltype(f(std::declval<T const&>()))> >
  {
    auto self = std::move(*this);
    return std::async( std::launch::async,
      [f, self]() mutable ->decltype(std::declval<F&>()(std::declval<T const&>())){
        return f(self.get());
      }
    );
  }
};

现在,您的Async函数返回my_future<Output>

您只需使用->*链接它们。

专攻void个:

template<>
struct my_future<void>:std::shared_future<void> {
  using std::shared_future<void>::shared_future;

  template<class F>
  auto operator->*( F f )&&
  -> my_future< std::decay_t<decltype(f())> >
  {
    auto self = std::move(*this);
    return std::async( std::launch::async,
      [f, self]() mutable ->decltype(std::declval<F&>()()){
        self.wait();
        return f();
      }
    );
  }
};

测试代码:

my_future<int> f = std::async( []{ return 7; } );
auto f2 = std::move(f)->*[](int x){ return x*2; };
std::cout << f2.get() << '\n';
auto f3 = std::move(f2)->*[](int y){}->*[]{ return 42; };
std::cout << f3.get() << '\n';

Live example

基本上这是在实施.then并使用future s。

您的代码变为:

fooAsync(input)
->*[=](Output output) {
  // ...
  return asyncA(output);
}
->*[=](OutputA outputA) {
  // ...
  return asyncB(outputA);
}
->*[=](OutputB outputB) {
  // ...
  return asyncC(outputB);
}
->*[=](OutputC outputC) {
  // ...
  return asyncD(outputC);
}
->*[=](OutputD outputD) {
  ...;
};

请注意,可以在其他地方生成,存储,传递和处理中间值。

另请注意,可以增加期货以启动更少的线程。

想象一下,您的增强型未来有可选&#34;延续&#34; (返回void)小心地用互斥量包裹。

以下是伪代码:

template<class T>
struct future_state {
  mutable std::mutex m;
  std::condition_variable cv;
  std::optional<T> value;
  std::function<void(T&&)> continuation;

  void set_value( T&& t ) {
    {
      auto l = lock();
      if (continuation)
        return continuation( std::forward<T>(t) );
      value.emplace( std::forward<T>(t) );
    }
    cv.notify_one();
  }
  bool has_value() const {
    auto l = lock();
    return (bool) value;
  }
  bool has_continuation() const {
    auto l = lock();
    return (bool) continuation;
  }
  std::unique_lock<std::mutex> lock() const {
    return std::unique_lock<std::mutex>(m);
  }
  T get_value() const { 
    auto l = lock();
    cv.wait( l, [&]{ return (bool)value; } );
    T r  = std::move(*value);
    value = std::nullopt;
    return r;
  }
  // etc
};

template<>
struct future_state<void> {
  // TODO

现在使用std::shared_futureshared_ptr<future_state>方法围绕.then包围->*类似界面,或者附加延续(如果旧版本未完成) )或者用新的努力启动一个新的线程(如果旧的完成),并返回结果的未来。

现在您的异步代码可能更喜欢管理自己的线程/异步。 A&#34;同步延续&#34;如果任务已经准备就绪,那么没有启动新线程的工作相当不错,并且如果它还没有准备就可以继续未来状态。

这一切都不容易;它是线程代码。查看.then std::future的实施可能具有启发性。