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

CleanupAwaitable

An executor‐bound awaitable that runs the generator's cleanup logic.

CleanupSemiAwaitable

A semi‐awaitable for generator cleanup that is not yet bound to an executor.

NextAwaitable

An executor‐bound awaitable that yields the generator's next value.

NextResult

Holds the result of a single next() step: either a value or nothing.

NextSemiAwaitable

A semi‐awaitable produced by next() that is not yet bound to an executor.

Type Aliases

Name

Description

folly_private_safe_alias_t

Reports the safe‐alias level of this generator to the SafeAlias machinery.

pointer

The pointer type corresponding to the yielded reference.

promise_type

The promise type driving an AsyncGenerator coroutine.

reference

The reference type yielded by the generator.

value_type

The decayed value type produced by the generator.

Member Functions

Name

Description

AsyncGenerator [constructor]

Constructors

~AsyncGenerator [destructor]

Destroys the generator and its associated coroutine, if any.

operator=

Move‐assigns from another generator, destroying any current coroutine.

cleanup

Produces a semi‐awaitable that runs the generator's cleanup logic.

next

Produces a semi‐awaitable that yields the generator's next value.

swap

Swaps the underlying coroutine handles of two generators.

Friends

Name

Description

folly::coro::AsyncGenerator::promise_type

The promise type driving an AsyncGenerator coroutine.

folly::coro::tag_invoke

Customization point that builds an AsyncGenerator from a callable and args.

Non-Member Functions

Name

Description

accumulate

Accumulate the values from an input stream using a binary operation.

accumulate

Accumulate the values from an input stream into a single value, similar to std::accumulate.

concat

Concatenate the values from multiple streams into a single stream such that each stream is exhausted before the next one begins.

filter

Filters a stream, yielding only values that satisfy the predicate.

makeUnorderedAsyncGenerator

Yield the results of a range of awaitables in order of completion.

makeUnorderedAsyncGenerator

Yield the results of a range of awaitables in order of completion.

makeUnorderedTryAsyncGenerator

Yield Try results of a range of awaitables in order of completion.

makeUnorderedTryAsyncGenerator

Yield Try results of a range of awaitables in order of completion.

merge

Merges a stream of input streams into a single interleaved output stream.

merge

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