使用回调异步未来.C++ 11

use*_*447 4 asynchronous future asynccallback promise c++11

我有一份期货清单.问题是我有很多文件,我需要在创建每个文件后进行一些长时间的操作.这就是我想在每次"文件保存"后进行回调的原因.

例如,

  (new thread; saveFile 1.txt -> new thread; do a long operation after the file has been created)
  (new thread; saveFile 2.pdf -> new thread; do a long operation after the file has been created).
Run Code Online (Sandbox Code Playgroud)

我需要在一个单独的线程中做所有事情.保存文件至关重要,第二个任务无法在创建文件之前运行.我该怎么做?我有以下代码:

 void save_file() {
     // preparing data...
     saving a file
   } 

   std::vector<std::future<void>> saveFileTasks;
   for (int n = 0; n < p.size(); ++n)
   {
      saveFileTasks.push_back(std::async(std::bind(&saveFile, filename)));
   }

   for (auto &e : saveFileTasks) {
      e.get();
   }
Run Code Online (Sandbox Code Playgroud)

如何使用future/promise在C++ 11中进行回调?我不允许在我的项目中使用boost.

我真的很困惑,一个非常简单的任务有很多复杂的例子.很多例子都无法编译,例如,在C++ 11中不存在promise.set_wait_callback,但许多函数已经迁移到C++ 11.如果我使用Python或Clojure,我可以很容易地做到这一点.我怎么能用C++做到这一点?

Yak*_*ont 6

在未来,未来将有一个.then运算符,可以让您链接任务.

缺乏它我们可以写它.

// complete named operator library in about a dozen lines of code:
namespace named_operator {
  template<class D>struct make_operator{ constexpr make_operator() {}; };

  template<class T, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }
  template<class Lhs, class Op, class Rhs>
  decltype(auto) operator*( half_apply<Lhs, Op>&& lhs, Rhs&& rhs )
  {
    return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

// create a named operator then:
namespace then_ns {
  static const struct then_t:named_operator::make_operator<then_t> {} then{};

  namespace details {
    template<size_t...Is, class Tup, class F>
    auto invoke_helper( std::index_sequence<Is...>, Tup&& tup, F&& f )
    ->decltype(std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... ))
    {
      return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
    }
  }

  // first overload of A *then* B handles tuple and tuple-like return values:
  template<class Tup, class F>
  auto named_invoke( Tup&& tup, then_t, F&& f )
  -> decltype( details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ) )
  {
    return details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
  }

  // second overload of A *then* B
  // only applies if above does not:
  template<class T, class F>
  auto named_invoke( T&& t, then_t, F&& f, ... )
  -> std::result_of_t< F(T) >
  {
    return std::forward<F>(f)(std::forward<T>(t));
  }
  // *then* with a future; unpack the future
  // into a call to f within an async:
  template<class X, class F>
  auto named_invoke( std::future<X> x, then_t, F&& f )
  -> std::future< std::decay_t<decltype( std::move(x).get() *then* std::declval<F>() )> >
  {
    return std::async( std::launch::async,
      [x = std::move(x), f = std::forward<F>(f)]() mutable {
        return std::move(x).get() *then* std::move(f);
      }
    );
  }
  // void future, don't try to pass void to f:
  template<class F>
  auto named_invoke( std::future<void> x, then_t, F&& f )
  -> std::future< std::decay_t<decltype( std::declval<F>()() )> >
  {
    return std::async( std::launch::async,
      [x = std::move(x), f = std::forward<F>(f)]() mutable {
        std::move(x).get();
        return std::move(f)();
      }
    );
  }
}
using then_ns::then;
Run Code Online (Sandbox Code Playgroud)

看,那并不难.

a *then* f,如果a是元组(或对或数组),将调用f内容a.

如果a不是元组等,或f不接受的内容,a这样一来,它调用fa.

如果a未来,它会创建一个消耗a.get()使用的新异步未来*then*.

实例.

假设您想在保存文件时增加原子int:

std::vector<std::future<void>> saveFileTasks;
for (int n = 0; n < p.size(); ++n)
{
  saveFileTasks.push_back(
    std::async(std::launch::async, [filename]{
      saveFile(filename);
    })
  );
}
std::atomic<int> count;
for (auto &e : saveFileTasks) {
  e = std::move(e) *then* [&count]{
    ++count;
  });
}
Run Code Online (Sandbox Code Playgroud)

当然,这可以在没有命名的运算符*then*样式语法的情况下完成,但有趣的是什么?

如果第一个异步返回一个元组,则第二个异步可以将其作为元组或解包的"平面"参数.

  • 对于那些必须深入研究您的代码的人来说,这没什么乐趣。 (3认同)
  • @ram 天哪,我永远不会为专业项目提出这个。C++ 中的命名运算符是一个可爱的玩具,但如果没有实践标准,使用时的明确意图就会被巴洛克式的实现要求所淹没。如果没有实践标准,用户就必须了解其实现,这对于大多数 C++ 程序员来说是不可能的。OTOH,编写代码很有趣,所以当有人问一个关于 SO 的不可能的问题时,我有时会说“看起来这是可能的”。 (3认同)
  • @doc我必须同意代码很糟糕并且不必要地复杂。这些东西不会通过大多数代码审查,我会为看到这个泄漏到他们的源代码树中的团队感到遗憾。 (2认同)

Vit*_*meo 5

不幸的是.then,当前版本没有延续std::future- 它与类似的实用程序一起提出用于未来的C++标准.

如果你不能使用boost,你可以用函数组合构建自己的延续:

string save_file(string data)      { /* ... */ return filename; } // step 1
void do_operation(string filename) { /* ... */ }                  // step 2

// ... 

std::vector<std::future<void>> fileTasks;
for(const auto& data : /* ... */)
{
    fileTasks.emplace_back(std::async(std::launch::async, 
        [data]{ do_operation(save_file(data)); });
}
Run Code Online (Sandbox Code Playgroud)

请注意,std::async([data]{ do_operation(save_file(data)); }它将在同一个线程中执行这两个函数.如果您希望在单独的线程中执行每个函数,您可以async多次调用:

std::async(std::launch::async, [data]
{
    auto result = save_file(data);
    std::async(std::launch::async, [r = std::move(result)]
    {
        do_operation(std::move(r));
    });
});
Run Code Online (Sandbox Code Playgroud)

使用boost::future该标准的任一版本或未来版本,您只需说:

std::async(std::launch::async, [data]{ save_file(data); })
    .then([](string filename){ do_operation(filename); );
Run Code Online (Sandbox Code Playgroud)