Consider the following example taken from N3650:
int cnt = 0;
do {
cnt = await streamR.read(512, buf);
if (cnt == 0)
break;
cnt = await streamW.write(cnt, buf);
} while (cnt > 0);
I am probably missing something, but if I understood async
and await
well, what is the point in showing the usefulness of the two constructs with the above example when the effects are equivalent to writing:
int cnt = 0;
do {
cnt = streamR.read(512, buf).get();
if (cnt == 0)
break;
cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);
where both the read().get()
and write().get()
calls are synchronous?
The await keyword is not equal to calling get on a future. You might look at it more like this, suppose you start from this:
future<T> complex_function()
{
do_some_stuff();
future<Result> x = await some_async_operation();
return do_some_other_stuff(x);
}
This is functionally more or less the same as
future<T> complex_function()
{
do_some_stuff();
return some_async_operation().then([=](future<Result> x) {
return do_some_other_stuff(x);
});
}
Note the more or less, because there are some resource management implications, variables created in do_some_stuff
shouldn't be copied to execute do_some_other_stuff
like the lambda version will do.
The second variant makes it more clear what will happen upon invocation.
do_some_stuff()
will be invoked synchronously when you call complex_function
.some_async_operation
is called asynchronously and results in a future. The exact moment when this operation is executed depends on your actual asynchronous calling implementation, it might be immediate when you use threads, it might be whenever the .get()
is called when you use defered execution. do_some_other_stuff
immediately, but rather chain it to the future obtained in step 2. This means that it can be executed as soon as the result from some_async_operation
is ready but not before. Aside from that, it's moment of execution is determined by the runtime. If the implementation would just wrap the then
proposal, this means it would inherit the parent future's executor/launch policy (as per N3558).A more complete example (hopefully correct):
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();
The important point is (quoting from the draft):
After suspending, a resumable function may be resumed by the scheduling logic of the runtime and will eventually complete its logic, at which point it executes a return statement (explicit or implicit) and sets the function’s result value in the placeholder.
and:
A resumable function may continue execution on another thread after resuming following a suspension of its execution.
That is, the thread who originally called forwardMsgs
can return at any of the suspension points. If it does, during the /* do something */
line, the code inside forwardMsgs
can be executed by another thread even though the function has been called "synchronously".
This example is very similar to
future<void> fut = std::async(forwardMsgs, myStreamR, myStreamW);
/* do something */
fut.get();
The difference is the resumable function can be executed by different threads: a different thread can resume execution (of the resumable function) after each resumption/suspension point.
I think the idea is that the streamR.read()
and streamW.write()
calls are asynchronous I/O operations and return futures, which are automatically waited on by the await
expressions.
So the equivalent synchronous version would have to call future::get()
to obtain the results e.g.
int cnt = 0;
do {
cnt = streamR.read(512, buf).get();
if (cnt == 0)
break;
cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);
You're correct to point out that there is no concurrency here. However in the context of a resumable function the await
makes the behaviour different to the snippet above. When the await
is reached the function will return a future
, so the caller of the function can proceed without blocking even if the resumable function is blocked at an await
while waiting for some other result (e.g. in this case the read()
or write()
calls to finish.) The resumable function might resume running asynchronously, so the result becomes available in the background while the caller is doing something else.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With