Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion for Understanding Operator co_await #2

Open
lewissbaker opened this issue Nov 17, 2017 · 31 comments
Open

Discussion for Understanding Operator co_await #2

lewissbaker opened this issue Nov 17, 2017 · 31 comments

Comments

@lewissbaker
Copy link
Owner

Please add comments here to discuss or provide feedback on https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await

@gamecentric
Copy link

Very good article, thanks for sharing.

@Paultok
Copy link

Paultok commented Dec 3, 2017

This is a great article! Thank you!

@jupp0r
Copy link

jupp0r commented Jan 18, 2018

Really good article, thanks a lot. I'm excited about the next one on the promise type.

@Omnifarious
Copy link

I'm co_awaiting your next installment.

@Dwood15
Copy link

Dwood15 commented Aug 10, 2018

It looks as if coroutines were removed from the proposed standard? I can't find any reference to them on cppreference

@lewissbaker
Copy link
Owner Author

Coroutines have not yet been adopted into the C++ draft standard, but I'm still hopeful they will be for C++20 :)

See this cppreference page for the reference to the latest version of the Coroutines TS:
https://en.cppreference.com/w/cpp/experimental

@ddosoff
Copy link

ddosoff commented Nov 22, 2018

How I can compile examples?

@lewissbaker
Copy link
Owner Author

@ddosoff You can use Compiler Explorer to compile cppcoro examples. You just need to enable the cppcoro library in the “Libraries” drop down. Alternatively check out the cppcoro repository locally and follow the build instructions in thr Readme.

@rondarz
Copy link

rondarz commented Mar 1, 2019

Interesting to compare the amount of code (and complexity) in the C++ version of an async event and the C# one. Why does it have to be so complex (linked lists, atomic types)?

@insoulity
Copy link

Hi Lewiss, thanks again for the great article.

Based on some examples and n4736, await_suspend could return not only void and bool but also coroutine_handle as well. When could it be useful? Also then how would compiler translation look like with it?

@gamecentric
Copy link

gamecentric commented Mar 4, 2019

@insoulity the possibility to have await_suspend return coroutine_handle has been introduced recently by paper p0913. I'll suggest you read the paper (it's very short).

@insoulity
Copy link

insoulity commented Mar 4, 2019

@iaanus Thanks for the reference. FYI one example with such an awaiter type I saw before was Nano-coroutine example by Gor's last year Cppcon.

@lewissbaker
Copy link
Owner Author

Interesting to compare the amount of code (and complexity) in the C++ version of an async event and the C# one. Why does it have to be so complex (linked lists, atomic types)?

The C# implementation will typically make use of the Task and TaskCompletionSource classes which hide a lot of the thread synchronisation in their internals. Also, use of these classes requires heap-allocating the state for storing the result/continuation.

The implementation for C++ performs no heap allocation directly - it uses storage space from the coroutine frame and stitches these together into a linked list.

@lewissbaker
Copy link
Owner Author

@insoulity The compiler translation for the symmetric-transfer version of await_suspend() is pretty straight forward:

// ... etc. as for other variants.
if (!awaiter.await_ready()) {
  // suspend-point
  awaiter.await_suspend(coroutine_handle<promise_type>::from_promise(promise)).resume();
  // return-to-caller-or-resumer
  // resume-point
}
awaiter.await_resume();

The really interesting thing about this variant is that the compiler is performs a guaranteed tail-call to the .resume() call on the returned coroutine_handle which allows us to do recursive coroutines without consuming stack-space.

See the latest version of cppcoro::task<T> for an example of how this can simplify and make more efficient the implementation of some coroutines/awaitables.

I've been meaning to write up more detail about the benefits of symmetric-transfer and hope to do so soon.

@Gadamatik
Copy link

Hello Lewis,
Great article... In the section "Synchronisation-free async code", you mentioned that one can take advantage of await_suspend() by publishing the handle to another thread that can later resume the coroutine associated to that handle and concurrently run with await_suspend()... Is that safe to access this coroutine while another thread is also using it... I think that I need to make sure about accessing the shared data, am I wrong??? This situations cannot lead to undefined Behavior??? Please correct me if I misunderstood...

Thanks a lot for your time and articles

@Celthi
Copy link

Celthi commented Sep 11, 2020

Should co_await be co_yield in the following para?

The Promise interface specifies methods for customising the behaviour of the coroutine itself. The library-writer is able to customise what happens when the coroutine is called, what happens when the coroutine returns (either by normal means or via an unhandled exception) and customise the behaviour of any co_await or co_yield expression within the coroutine.

@subbota-a
Copy link

subbota-a commented Oct 4, 2020

Hello @lewissbaker !

Is it right that async_manual_reset_event resumes all awaiting consumers on the single thread? So, there were many awaiting threads but they will wake up on the single thread?

@lewissbaker
Copy link
Owner Author

@subbota-a Yes, the implementation of async_manual_reset_event will resume all awaiting consumers on the thread that calls event.set() inside the call to set().

A future enhancement might require the awaiting coroutine to provide a scheduler that will be used to schedule the resumption of the coroutine at a later time rather than resuming it inline inside the call to .set(). This is the kind of thing that e.g. folly::coro::Baton does (which is similar to async_manual_reset_event).

@martinbonaciugome
Copy link

Hello @lewissbaker,

I was wondering if this portion of the article is accurate?

{
  auto&& value = <expr>;
  auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
  auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));

as this would suggest that if get_awaitable and/or get_awaiter produced values, their lifetimes would be limited to the scope of this. clang 10 currently doesn't function like this, and I would like to file a bug with them, as it's very beneficial to have an awaiter's lifetime effectively hidden from the caller

@lewissbaker
Copy link
Owner Author

@martinbonaciugome Yes, I think technically if get_awaitable() and/or get_awaiter() returns a prvalue then the lifetime of those objects will extend to the end of the full-expression, rather than their lifetimes being limited to the scope of the co_await expression.

The auto&& could be decltype(auto), although I think this is roughly equivalent due to the automatic lifetime extension when a prvalue is used to initialise a reference.

@martinbonaciugome
Copy link

martinbonaciugome commented Dec 10, 2020

Thanks for the quick reply. That's a shame, as these objects are completely hidden and are just plumbing, yet will cause a lot more code and inefficiencies to handle coroutine destruction during suspension, and will bloat the coroutine frame substantially if co_await is used in subexpressions for long running deep coroutines like UI.

@cadenzasong
Copy link

Thanks for the series. In the code skeleton in section "Awaiting the Awaiter"

Should the p in the 3 occurrences of:
handle_t::from_promise(p)
be promise as used previously in:
auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
?

Thanks

@cadenzasong
Copy link

Nit typo report:

In the ASCII graph in section "Synchronisation-free async code":
<supend-point> -> <suspend-point>

@muusbolla
Copy link

Again about this section, bit of a noob question here...

auto&& value = <expr>; auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value)); auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));

What exactly is the purpose of the static_cast<decltype(awaitable)> expressions? Is it to turn possible named rvalue references back into rvalues instead of them being copied? How is this functionally different from using std::forward?

@muusbolla
Copy link

muusbolla commented Mar 23, 2022

For the following section:

void async_manual_reset_event::reset() noexcept { void* oldValue = this; m_state.compare_exchange_strong(oldValue, nullptr, std::memory_order_acquire); }

Shouldn't it be std::memory_order_release? You need to release any writes that occured prior to calling reset() to any thread that just checks the status of the event with is_set which uses std::memory_order_acquire.

edit: I see in the github source you've changed reset() to use relaxed instead. This makes more sense than acquire, but I still think it should be release.

@snawaz
Copy link

snawaz commented Nov 3, 2022

@lewissbaker

This article was published on 2017 and the spec for coroutine was released on 2020. Just wondering if this article, and other (coroutine) articles on your blog, are still conforming to the 2020 spec? 🤔

It'd be great if you put a comment/disclaimer (on the top) mentioning the status of the articles, just to avoid such confusions, or mistakenly misleading the C++ programmers (in case the articles are not conforming anymore). 😄

@rturrado
Copy link

rturrado commented Dec 5, 2022

Hi Lewis,

  • Minor typo in Compiler <-> Library Interaction section, line There are two kinds of interfaces that are defined by the coroutines TS: The Promise interface and the Awaitable interface.: it says The Promise, instead of the Promise.
  • Obtaining the Awaiter section: why using static_cast<T&&> instead of std::foward<T> in this section's code?

Thanks!

@lewissbaker
Copy link
Owner Author

why using static_cast<T&&> instead of std::forward<T> in this section's code?

These two are equivalent. I sometimes write static_cast<T&&> to reduce compile-times as it doesn't need to instantiate a function template. However, I think recent versions of clang now treat std::forward<T> specially so there may not be a difference any more, at least on clang.

@scuzqy
Copy link

scuzqy commented Oct 21, 2023

Hi lewissbaker,

I noticed that a friend class declaration of async_manual_reset_event is missing within struct async_manual_reset_event::awaiter.

Your source on godbolt declared it though(line 49).

@segsch
Copy link

segsch commented May 4, 2024

Hi Lewis,

Great article and series! Thanks! 🙏

A minor comment:
This can be removed from the example: friend struct awaiter; , as awaiter is a nested class.

@libbooze
Copy link

libbooze commented Dec 2, 2024

Godbolt link expired since it uses clang trunk, I think this is ok update,
I had to add noexcept, remove experimanetal, beside that I just changed c++ version in compiler args

https://godbolt.org/z/7ePWvoPrz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests