The AsyncGenerator class represents a sequence of asynchronously produced values where the values are produced by a coroutine.
Synopsis
Declared in <folly/coro/AsyncGenerator.h>
template<
typename Reference,
typename Value = remove_cvref_t<Reference>,
bool RequiresCleanup = false>
class [[nodiscard]] AsyncGenerator;
Description
Values are produced by using the 'co_yield' keyword and the coroutine can also consume other asynchronous operations using the 'co_await' keyword. The end of the sequence is indicated by executing 'co_return;' either explicitly or by letting execution run off the end of the coroutine.
Reference Type ‐‐‐‐‐‐‐‐‐‐‐‐‐‐ The first template parameter controls the 'reference' type. i.e. the type returned when you dereference the iterator using operator*(). This type is typically specified as an actual reference type. eg. 'const T&' (non‐mutable), 'T&' (mutable) or 'T&&' (movable) depending what access you want your consumers to have to the yielded values.
It's also possible to specify the 'Reference' template parameter as a value type. In this case the generator takes a copy of the yielded value (either copied or move‐constructed) and you get a copy of this value every time you dereference the iterator with '*iter'. This can be expensive for types that are expensive to copy, but can provide a small performance win for types that are cheap to copy (like built‐in integer types).
Value Type ‐‐‐‐‐‐‐‐‐‐ The second template parameter is optional, but if specified can be used as the value‐type that should be used to take a copy of the value returned by the Reference type. By default this type is the same as 'Reference' type stripped of qualifiers and references. However, in some cases it can be a different type. For example, if the 'Reference' type was a non‐reference proxy type.
Example:
AsyncGenerator<std::tuple<const K&, V&>, std::tuple<K, V>> getItems() { auto firstMap = co_await getFirstMap(); for (auto&& [k, v]: firstMap) { co_yield {k, v}; } auto secondMap = co_await getSecondMap(); for (auto&& [k, v]: secondMap) { co_yield {k, v}; } }
This is mostly useful for generic algorithms that need to take copies of elements of the sequence.
Executor Affinity ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ An AsyncGenerator coroutine has similar executor‐affinity to that of the folly::coro::Task coroutine type. Every time a consumer requests a new value from the generator using 'co_await ++it' the generator inherits the caller's current executor. The coroutine will ensure that it always resumes on the associated executor when resuming from `co_await' expression until it hits the next 'co_yield' or 'co_return' statement. Note that the executor can potentially change at a 'co_yield' statement if the next element of the sequence is requested from a consumer coroutine that is associated with a different executor.
Example: Writing an async generator.
folly::coro::AsyncGenerator<Record&&> getRecordsAsync() { auto resultSet = executeQuery(someQuery); for (;;) { auto resultSetPage = co_await resultSet.nextPage(); if (resultSetPage.empty()) break; for (auto& row : resultSetPage) { co_yield Record{row.get("name"), row.get("email")}; } } }
Example: Consuming items from an async generator
folly::coro::Task<void> consumer() { auto records = getRecordsAsync(); while (auto item = co_await records.next()) { auto&& record = *item; process(record); } }
Async Cleanup ‐‐‐‐‐‐‐‐‐‐‐‐‐ When the template parameter RequiresCleanup is true, the owner of an AsyncGenerator is responsible for awaiting cleanup() before the generator object's destructor is called. That allows to use folly::coro::co_scope_exit awaitables inside AsyncGenerator, which are asynchronously executed when cleanup() is awaited. Note that the AsyncGenerator coroutine frame is destroyed before co_scope_exit awaitables are executed.
There is an alias CleanableAsyncGenerator for AsyncGenerator with RequiresCleanup set to true.
Drain safety ‐‐‐‐‐‐‐‐‐‐‐‐ One significant difference between AsyncGenerator and folly::coro::Task is that AsyncGenerator may be destroyed between next() calls ‐ i.e. destroyed without being fully drained.
For example:
AsyncGenerator<int> gen() { SCOPE_EXIT { LOG(INFO) << "Step 4"; }; LOG(INFO) << "Step 1"; co_yield 41; SCOPE_EXIT { LOG(INFO) << "Step 3"; }; LOG(INFO) << "Step 2"; co_yield 42; SCOPE_EXIT { LOG(INFO) << "Never reached"; }; LOG(INFO) << "Never reached"; co_yield 43; }
{ AsyncGenerator<int> g = gen(); while (auto next = co_await g.next()) { LOG(INFO) << *next; if (*next == 42) { break; // ˆ this may trigger generator destruction before it is drained. } } }
This means that when writing an AsyncGenerator, you should always document whether such AsyncGenerator requires draining before destruction (drain unsafe). When possible you should always aim to make AsyncGenerator not require draining before destruction (drain safe).
If an AsyncGenerator is drain unsafe, always mention this in the documentation and ideally include some assertions that help detect cases where such AsyncGenerator is destroyed without being fully drained.
Example:
AsyncGenerator<int> gen() { auto drainGuard = makeGuard([]{ LOG(FATAL) << "I shall be drained!"; }); co_yield 41; co_yield 42; co_yield 43; drainGuard.dismiss(); }
Types
Name |
Description |
An executor‐bound awaitable that runs the generator's cleanup logic. |
|
A semi‐awaitable for generator cleanup that is not yet bound to an executor. |
|
An executor‐bound awaitable that yields the generator's next value. |
|
Holds the result of a single next() step: either a value or nothing. |
|
A semi‐awaitable produced by next() that is not yet bound to an executor. |
Type Aliases
Name |
Description |
Reports the safe‐alias level of this generator to the SafeAlias machinery. |
|
The pointer type corresponding to the yielded reference. |
|
The promise type driving an AsyncGenerator coroutine. |
|
The reference type yielded by the generator. |
|
The decayed value type produced by the generator. |
Member Functions
Name |
Description |
|
Constructors |
|
Destroys the generator and its associated coroutine, if any. |
Move‐assigns from another generator, destroying any current coroutine. |
|
Produces a semi‐awaitable that runs the generator's cleanup logic. |
|
Produces a semi‐awaitable that yields the generator's next value. |
|
Swaps the underlying coroutine handles of two generators. |
Friends
Name |
Description |
The promise type driving an AsyncGenerator coroutine. |
|
Customization point that builds an AsyncGenerator from a callable and args. |
Non-Member Functions
Name |
Description |
Accumulate the values from an input stream using a binary operation. |
|
Accumulate the values from an input stream into a single value, similar to |
|
Concatenate the values from multiple streams into a single stream such that each stream is exhausted before the next one begins. |
|
Filters a stream, yielding only values that satisfy the predicate. |
|
Yield the results of a range of awaitables in order of completion. |
|
Yield the results of a range of awaitables in order of completion. |
|
Yield Try results of a range of awaitables in order of completion. |
|
Yield Try results of a range of awaitables in order of completion. |
|
Merges a stream of input streams into a single interleaved output stream. |
|
Merges a stream of input streams into a single interleaved output stream. |
Return Value
|
Note
|
The return value should not be discarded. |
Created with MrDocs