16

考虑以下取自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()同步的?

4

5 回答 5

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被异步调用并导致未来。执行此操作的确切时间取决于您的实际异步调用实现,当您使用线程时可能是立即的,.get()当您使用延迟执行时可能是在任何时候调用。
  3. 我们不会do_some_other_stuff立即执行,而是将其链接到步骤 2 中获得的未来。这意味着它可以在结果some_async_operation准备好后立即执行,而不是之前。除此之外,它的执行时刻由运行时决定。如果实现只是包装then提案,这意味着它将继承父未来的执行者/启动策略(根据 N3558)。
  4. 该函数返回代表最终结果的最后一个未来。请注意,这需要成为未来,因为函数体的一部分是异步执行的。
于 2013-08-20T09:20:06.853 回答
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();

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

挂起后,可恢复函数可能会被运行时的调度逻辑恢复并最终完成其逻辑,此时它会执行返回语句(显式或隐式)并将函数的结果值设置在占位符中。

和:

可恢复函数可以在暂停执行后恢复后继续在另一个线程上执行。

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


这个例子与

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

/* do something */

fut.get();

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

于 2013-08-20T08:29:57.827 回答
3

我认为这个想法是streamR.read()andstreamW.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,函数将返回 a future,因此即使可恢复函数await在等待其他结果时被阻塞(例如,在这种情况下,read()write()调用完成),函数的调用者也可以继续进行而不会阻塞。可能会恢复异步运行,因此在调用者执行其他操作时结果在后台可用。

于 2013-08-20T08:08:56.380 回答
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

于 2013-08-20T09:44:13.843 回答
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);

你仍然需要至少三个堆栈。在这两种情况下,主线程都没有被阻塞。是否假设编译器会比 future<>:get() 更有效地实现 await?好吧,现在可以使用没有await的那个。

谢谢亚当齐林斯基

于 2014-02-03T12:53:42.083 回答