了解有关C ++ 1y提案N3650中可恢复功能的示例

时间:2013-08-20 07:38:33

标签: c++ c++14

考虑以下来自N3650的示例:

int cnt = 0;
do {
   cnt = await streamR.read(512, buf);
   if (cnt == 0)
      break;
   cnt = await streamW.write(cnt, buf);
} while (cnt > 0);

我可能遗漏了一些东西,但是如果我理解asyncawait那么,当效果等同于写作时,用上面的例子展示两个结构的用处有什么意义:

int cnt = 0;
do {
   cnt = streamR.read(512, buf).get();
   if (cnt == 0)
      break;
   cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);

read().get()write().get()调用是同步的吗?

5 个答案:

答案 0 :(得分:7)

await关键字不等于将来调用get。您可能会更喜欢这样看,假设您从这开始:

future<T> complex_function()
{
     do_some_stuff();
     future<Result> x = await some_async_operation();
     return do_some_other_stuff(x);
}

这在功能上与

大致相同
future<T> complex_function()
{
     do_some_stuff();
     return some_async_operation().then([=](future<Result> x) {
         return do_some_other_stuff(x);
     });
}

或多或少注意,因为存在一些资源管理问题,不应将do_some_stuff中创建的变量复制到执行do_some_other_stuff,就像lambda版本一样。

第二个变体使得在调用时会发生什么变得更清楚。

  1. 当您致电do_some_stuff()时,complex_function会同步调用。
  2. some_async_operation被异步调用并导致未来。执行此操作的确切时刻取决于您的实际异步调用实现,它可能在您使用线程时立即执行,可能是在您使用deferred执行时调用.get()时。
  3. 我们不会立即执行do_some_other_stuff,而是将其链接到步骤2中获得的未来。这意味着只要some_async_operation的结果准备就绪,就可以执行它,但之前不会执行。除此之外,它的执行时刻由运行时决定。如果实现只包装then提议,这意味着它将继承父未来的执行者/启动策略(根据N3558)。
  4. 该函数返回最后一个未来,表示最终结果。注意这个NEEDS是未来,因为函数体的一部分是异步执行的。

答案 1 :(得分:6)

一个更完整的例子(希望是正确的):

future<void> forwardMsgs(istream& streamR, ostream& streamW) async
{
    char buf[512];
    int cnt = 0;
    do {
       cnt = await streamR.read(512, buf);
       if (cnt == 0)
          break;
       cnt = await streamW.write(cnt, buf);
    } while (cnt > 0);
}

future<void> fut = forwardMsgs(myStreamR, myStreamW);

/* do something */

fut.get();

重要的一点是(引用草案):

  

暂停后,可恢复的函数可以由运行时的调度逻辑恢复,并最终完成其逻辑,此时它执行return语句(显式或隐式)并在占位符中设置函数的结果值。 / p>

  

可恢复功能可以在暂停执行后继续执行另一个线程。

也就是说,最初调用forwardMsgs 的线程可以在任何暂停点返回。如果是这样,在/* do something */行期间,forwardMsgs 中的代码可以由另一个线程执行,即使该函数已被“同步”调用”


此示例与

非常相似
future<void> fut = std::async(forwardMsgs, myStreamR, myStreamW);

/* do something */

fut.get();

区别在于可恢复功能可以由不同的线程执行:不同的线程可以在每个恢复/暂停点之后恢复执行(可恢复功能)。

答案 2 :(得分:3)

我认为我的想法是streamR.read()streamW.write()调用是异步I / O操作并返回期货,await表达式会自动等待它们。

因此,等效的同步版本必须调用future::get()才能获得结果,例如

int cnt = 0;
do {
   cnt = streamR.read(512, buf).get();
   if (cnt == 0)
      break;
   cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);

你指出这里没有并发性是正确的。但是,在可恢复功能的上下文中,await使行为与上面的代码段不同。当达到await时,函数将返回future,因此即使在等待其他结果时await阻止了可恢复功能,函数的调用者仍可继续进行而不会阻塞(例如,在这种情况下,read()write()调用完成。)可恢复的函数可能会异步恢复运行,因此当调用者正在执行其他操作时,结果将在后台运行。

答案 3 :(得分:2)

这是示例函数的正确转换,不使用await:

struct Copy$StackFrame {
  promise<void> $result;
  input_stream& streamR;
  output_stream& streamW;
  int cnt;
  char buf[512];
};
using Copy$StackPtr = std::shared_ptr<Copy$StackFrame>;

future<void> Copy(input_stream& streamR, output_stream& streamW) {
  Copy$StackPtr $stack{ new Copy$StackFrame{ {}, streamR, streamW, 0 } };
  future<int> f$1 = $stack->streamR.read(512, stack->buf);
  f$1.then([$stack](future<int> f) { Copy$Cont1($stack, std::move(f)); });
  return $stack->$result.get_future();
}

void Copy$Cont1(Copy$StackPtr $stack, future<int> f$1) {
  try {
    $stack->cnt = f$1.get();
    if ($stack->cnt == 0) {
      // break;
      $stack->$result.set_value();
      return;
    }
    future<int> f$2 = $stack->streamW.write($stack->cnt, $stack->buf);
    f$2.then([$stack](future<int> f) { Copy$Cont2($stack, std::move(f)); });
  } catch (...) {
    $stack->$result.set_exception(std::current_exception());
  }
}

void Copy$Cont2(Copy$StackPtr $stack, future<int> f$2) {
  try {
    $stack->cnt = f$2.get();
    // while (cnt > 0)
    if (cnt <= 0) {
      $stack->$result.set_value();
      return;
    }
    future<int> f$1 = $stack->streamR.read(512, stack->buf);
    f$1.then([$stack](future<int> f) { Copy$Cont1($stack, std::move(f)); });
  } catch (...) {
    $stack->$result.set_exception(std::current_exception());
  }
}

正如您所看到的,这里的编译器转换非常复杂。这里的关键点是,与get()版本不同,原始Copy会在第一次异步调用完成后立即返回其未来。

答案 4 :(得分:0)

我对这两个代码示例之间差异的含义有同样的问题。让我们重新写一点,以便更完整。

    // Having two functions
    future<void> f (istream&streamR, ostream&streamW) async
    {  int cnt = 0;
       do {
          cnt = await streamR.read(512, buf);
          if (cnt == 0)
             break;
          cnt = await streamW.write(cnt, buf);
       } while (cnt > 0);
    }
    void g(istream&streamR, ostream&streamW)
    {  int cnt = 0;
       do {
          cnt = streamR.read(512, buf).get();
          if (cnt == 0)
             break;
          cnt = streamW.write(cnt, buf).get();
       } while (cnt > 0);
    }
    // what is the difference between
    auto a = f(streamR, streamW);
    // and 
    auto b = async(g, streamR, streamW);

你仍然需要至少三个筹码。在这两种情况下,主线程都不会被阻止。是否假设await将比未来&lt;&gt;:get()?更有效地实现编译器。那么,没有等待的那个现在可以使用了。

由于 亚当·齐林斯基