-
Notifications
You must be signed in to change notification settings - Fork 87
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
Add support for Async Streams #606
base: draft-v8
Are you sure you want to change the base?
Conversation
> | ||
> *end example* | ||
|
||
Consumption of an asynchronous stream requires an `await foreach` to enumerate the elements of the stream. Adding the `await` keyword requires the method that enumerates the asynchronous stream to be declared with the `async` modifier and to return a type allowed for an async method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"requires" seems too strong; it is certainly possible to enumerate the items of an asynchronous stream by calling IAsyncEnumerator<T>.MoveNextAsync explicitly, without await foreach
. I thought perhaps "consumption" is a term that means only foreach
loops, but I didn't find the term used with such a meaning anywhere else in this repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like an odd paragraph in general. In particular, the "Adding the await
keyword" part feels ambiguous - adding it to what? I think we can make that a lot clearer, probably taking language from earlier in 15.15. (The program text for a method can include await
without it being asynchronous, due to local functions of course, so some care is required.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More later, but I think a good way to address this is to look at the statements section, and add a new subclause for "The await foreach" statement, rather than modifying the foreach statement section.
IAsyncEnumerator<T> GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default); | ||
``` | ||
|
||
> However, as we have not mentioned type `System.Threading.CancellationToken` in any other threading contexts in the spec, it has been omitted from the requirements, as if there was no argument expected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, System.Threading.CancellationToken
and System.Runtime.CompilerServices.EnumeratorCancellationAttribute
should be added to the standard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the C# standard defines IAsyncEnumerable<T> with IAsyncEnumerator<T> GetAsyncEnumerator();
, and a user-defined class implements IAsyncEnumerable<T> by explicit interface implementation:
using System;
using System.Collections.Generic;
class Class1 : IAsyncEnumerable<int> {
IAsyncEnumerator<int> IAsyncEnumerable<int>.GetAsyncEnumerator() {
throw new NotImplementedException();
}
}
then Class1 will not be portable between the standard and the .NET implementation, as the method signature will not match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we should add this to the standard and make everything match up. We could probably do it in a second PR, if that's easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s do it in this PR
@@ -1488,6 +1519,8 @@ While a mutual-exclusion lock is held, code executing in the same execution thre | |||
|
|||
## 12.14 The using statement | |||
|
|||
(Almost all of the text in this subclause was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) | |||
|
|||
The `using` statement obtains one or more resources, executes a statement, and then disposes of the resource. | |||
|
|||
```ANTLR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The using_statement grammar needs to allow await using
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is in #672
|
||
Local variables declared in a *resource_acquisition* are read-only, and shall include an initializer. A compile-time error occurs if the embedded statement attempts to modify these local variables (via assignment or the `++` and `--` operators), take the address of them, or pass them as `ref` or `out` parameters. | ||
|
||
A `using` statement is translated into three parts: acquisition, usage, and disposal. Usage of the resource is implicitly enclosed in a `try` statement that includes a `finally` clause. This `finally` clause disposes of the resource. If a `null` resource is acquired, then no call to `Dispose` is made, and no exception is thrown. If the resource is of type `dynamic` it is dynamically converted through an implicit dynamic conversion ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions)) to `IDisposable` during acquisition in order to ensure that the conversion is successful before the usage and disposal. | ||
A `using` statement is translated into three parts: acquisition, usage, and disposal. Usage of the resource is implicitly enclosed in a `try` statement that includes a `finally` clause. This `finally` clause disposes of the resource. If a `null` resource is acquired, then no call to `Dispose` (`DisposeAsync` for async streams) is made, and no exception is thrown. If the resource is of type `dynamic` it is dynamically converted through an implicit dynamic conversion ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions)) to `IDisposable` (`IAsyncDisposable` for async streams) during acquisition in order to ensure that the conversion is successful before the usage and disposal. | ||
|
||
A `using` statement of the form |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs similar lowering rules for await using
, particularly that the result of DisposeAsync
is awaited rather than discarded.
A comparison that doesn't include so many unrelated changes: draft-v7...RexJaeschke:csharpstandard:async-streams |
68c9e97
to
1f7e2b1
Compare
4f572a3
to
6bd175d
Compare
4b81f71
to
5b5d590
Compare
rebased on the latest draft v8 branch on 09/23/2023 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is close, but I think there are a few updates to make.
@@ -1109,25 +1111,25 @@ If the *foreach_statement* contains both or neither `ref` and `readonly`, the it | |||
|
|||
The iteration variable corresponds to a local variable with a scope that extends over the embedded statement. During execution of a `foreach` statement, the iteration variable represents the collection element for which an iteration is currently being performed. If the iteration variable denotes a read-only variable, a compile-time error occurs if the embedded statement attempts to modify it (via assignment or the `++` and `--` operators) or pass it as a `ref` or `out` parameter. | |||
|
|||
In the following, for brevity, `IEnumerable`, `IEnumerator`, `IEnumerable<T>` and `IEnumerator<T>` refer to the corresponding types in the namespaces `System.Collections` and `System.Collections.Generic`. | |||
In the following, for brevity, `IEnumerable` and `IEnumerator` refer to the corresponding types in the namespace `System.Collections`, and `IEnumerable<T>`, `IEnumerator<T>`, and `IAsyncEnumerator<T>` refer to the corresponding types in the namespace `System.Collections.Generic`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think a non-generic IAsyncEnumerable
and IAsyncEnumerator
exist. We should re-write this so it doesn't imply that they do.
|
||
The compile-time processing of a `foreach` statement first determines the ***collection type***, ***enumerator type*** and ***iteration type*** of the expression. This determination proceeds as follows: | ||
|
||
- If the type `X` of *expression* is an array type then there is an implicit reference conversion from X to the `IEnumerable` interface (since `System.Array` implements this interface). The collection type is the `IEnumerable` interface, the enumerator type is the `IEnumerator` interface and the iteration type is the element type of the array type `X`. | ||
- If the type `X` of *expression* is `dynamic` then there is an implicit conversion from *expression* to the `IEnumerable` interface ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions)). The collection type is the `IEnumerable` interface and the enumerator type is the `IEnumerator` interface. If the `var` identifier is given as the *local_variable_type* then the iteration type is `dynamic`, otherwise it is `object`. | ||
- Otherwise, determine whether the type `X` has an appropriate `GetEnumerator` method: | ||
- Perform member lookup on the type `X` with identifier `GetEnumerator` and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. | ||
- Perform member lookup on the type `X` with identifier `GetEnumerator` (`GetAsyncEnumerator` for async streams) and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This paragraph seems to imply a non-generic GetAsyncEnumerator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so - it's just saying that the GetEnumerator()
and GetAsyncEnumerator()
methods aren't generic to start (which is correct for both).
- Member lookup is performed on `E` with the identifier `Current` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. | ||
- Member lookup is performed on `E` with the identifier `MoveNext` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. | ||
- Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced and no further steps are taken. | ||
- Member lookup is performed on `E` with the identifier `MoveNext` (`MoveNextAsync` for async streams) and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this shouldn't apply to async streams, because of no non-generic types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think it's about the MoveNext
and MoveNextAsync
methods not being generic.
Co-authored-by: Bill Wagner <[email protected]>
@@ -5205,6 +5205,8 @@ For a discussion of the behavior when an exception is thrown from a finalizer, s | |||
|
|||
### 15.14.1 General | |||
|
|||
(Almost all of the text in this subclause and its siblings was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention to keep this part? It looks a little odd at the moment. I suspect it should at least be a note instead of just in parentheses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does feel a little odd. As an off the cuff suggestion:
- swap the order of §15.14 Iterators and §15.15 Async Functions
- rename "Iterators” to “Iterators and Async Streams”
- rewrite this new intro paragraph to say the clause covers synchronous and async iterators, the latter usually termed async streams.
- remove the parentheses and merge the content into the surrounding text
Of course this isn't the only “odd” thing we’ve done, e.g. property & indexer accessors with the “read exchanging terms” instruction.
@@ -5213,41 +5215,41 @@ When a function member is implemented using an iterator block, it is a compile-t | |||
|
|||
### 15.14.2 Enumerator interfaces | |||
|
|||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>`. For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>`, respectively. | |||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>` (`System.Collections.Generic.IAsyncEnumerator<T>` for async streams). For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>` (`IAsyncEnumerator<T>` for async streams), respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally finding this style really hard to read. I'd be happy to try to get this PR into a shape we want, merge it, and then I can create a new PR to change the style - assuming others feel the same way.
standard/classes.md
Outdated
|
||
#### 15.14.5.4 The Dispose method | ||
|
||
The `Dispose` method is used to clean up the iteration by bringing the enumerator object to the **after** state. | ||
The `Dispose` (`DisposeAync` for async streams) method is used to clean up the iteration by bringing the enumerator object to the **after** state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `Dispose` (`DisposeAync` for async streams) method is used to clean up the iteration by bringing the enumerator object to the **after** state. | |
The `Dispose` (`DisposeAsync` for async streams) method is used to clean up the iteration by bringing the enumerator object to the **after** state. |
> | ||
> *end example* | ||
|
||
Consumption of an asynchronous stream requires an `await foreach` to enumerate the elements of the stream. Adding the `await` keyword requires the method that enumerates the asynchronous stream to be declared with the `async` modifier and to return a type allowed for an async method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like an odd paragraph in general. In particular, the "Adding the await
keyword" part feels ambiguous - adding it to what? I think we can make that a lot clearer, probably taking language from earlier in 15.15. (The program text for a method can include await
without it being asynchronous, due to local functions of course, so some care is required.)
|
||
The compile-time processing of a `foreach` statement first determines the ***collection type***, ***enumerator type*** and ***iteration type*** of the expression. This determination proceeds as follows: | ||
|
||
- If the type `X` of *expression* is an array type then there is an implicit reference conversion from X to the `IEnumerable` interface (since `System.Array` implements this interface). The collection type is the `IEnumerable` interface, the enumerator type is the `IEnumerator` interface and the iteration type is the element type of the array type `X`. | ||
- If the type `X` of *expression* is `dynamic` then there is an implicit conversion from *expression* to the `IEnumerable` interface ([§10.2.10](conversions.md#10210-implicit-dynamic-conversions)). The collection type is the `IEnumerable` interface and the enumerator type is the `IEnumerator` interface. If the `var` identifier is given as the *local_variable_type* then the iteration type is `dynamic`, otherwise it is `object`. | ||
- Otherwise, determine whether the type `X` has an appropriate `GetEnumerator` method: | ||
- Perform member lookup on the type `X` with identifier `GetEnumerator` and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. | ||
- Perform member lookup on the type `X` with identifier `GetEnumerator` (`GetAsyncEnumerator` for async streams) and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so - it's just saying that the GetEnumerator()
and GetAsyncEnumerator()
methods aren't generic to start (which is correct for both).
- Member lookup is performed on `E` with the identifier `Current` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. | ||
- Member lookup is performed on `E` with the identifier `MoveNext` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. | ||
- Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced and no further steps are taken. | ||
- Member lookup is performed on `E` with the identifier `MoveNext` (`MoveNextAsync` for async streams) and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think it's about the MoveNext
and MoveNextAsync
methods not being generic.
- Member lookup is performed on `E` with the identifier `MoveNext` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. | ||
- Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced and no further steps are taken. | ||
- Member lookup is performed on `E` with the identifier `MoveNext` (`MoveNextAsync` for async streams) and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. | ||
- Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced, and no further steps are taken. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type of MoveNextAsync
is ValueTask<bool>
rather than bool
.
@@ -1309,6 +1311,33 @@ The order in which `foreach` traverses the elements of an array, is as follows: | |||
> | |||
> *end example* | |||
|
|||
An async enumerator may optionally expose a `DisposeAsync` method that may be invoked with no arguments and that returns something that can be `await`ed and whose `GetResult()` returns `void`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect there's a cleaner way of expressing this via task types, e.g. "with a return type that is a non-generic task type".
@@ -1797,6 +1826,8 @@ While a mutual-exclusion lock is held, code executing in the same execution thre | |||
|
|||
## 13.14 The using statement | |||
|
|||
(Almost all of the text in this subclause was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And another bit to remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like @jskeet I think this needs some reorg/rewriting to cover synchronous and asynchronous iterators more uniformly. Also need to address the omissions reported by @KalleOlaviNiemitalo .
@@ -5205,6 +5205,8 @@ For a discussion of the behavior when an exception is thrown from a finalizer, s | |||
|
|||
### 15.14.1 General | |||
|
|||
(Almost all of the text in this subclause and its siblings was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does feel a little odd. As an off the cuff suggestion:
- swap the order of §15.14 Iterators and §15.15 Async Functions
- rename "Iterators” to “Iterators and Async Streams”
- rewrite this new intro paragraph to say the clause covers synchronous and async iterators, the latter usually termed async streams.
- remove the parentheses and merge the content into the surrounding text
Of course this isn't the only “odd” thing we’ve done, e.g. property & indexer accessors with the “read exchanging terms” instruction.
@@ -5213,41 +5215,41 @@ When a function member is implemented using an iterator block, it is a compile-t | |||
|
|||
### 15.14.2 Enumerator interfaces | |||
|
|||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>`. For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>`, respectively. | |||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>` (`System.Collections.Generic.IAsyncEnumerator<T>` for async streams). For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>` (`IAsyncEnumerator<T>` for async streams), respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an example of what my opening off the cuff suggestion would produce this para would become:
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>` (`System.Collections.Generic.IAsyncEnumerator<T>` for async streams). For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>` (`IAsyncEnumerator<T>` for async streams), respectively. | |
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interfaces `System.Collections.Generic.IEnumerator<T>` and `System.Collections.Generic.IAsyncEnumerator<T>`. For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator`, `IEnumerator<T>` and `IAsyncEnumerator<T>` respectively. |
@@ -5213,41 +5215,41 @@ When a function member is implemented using an iterator block, it is a compile-t | |||
|
|||
### 15.14.2 Enumerator interfaces | |||
|
|||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>`. For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>`, respectively. | |||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>` (`System.Collections.Generic.IAsyncEnumerator<T>` for async streams). For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>` (`IAsyncEnumerator<T>` for async streams), respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think changing the name is a non-starter (flashbacks to NNRT here ;-)) and there is some convention in using the term “stream”. If my first suggestion above is followed the stream/async iterator stuff could be covered in §asynchronous-streams which will occur before the §15.15 Iterators and Async Streams clause.
|
||
### §asynchronous-streams Asynchronous stream creation and usage | ||
|
||
A stream can be created and consumed asynchronously. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The definition of “stream” needs to be added here, elsewhere in the Standard we have streams of Unicode characters and tokens, uses of StreamReader
etc.
See previous comments for how this might be done; it may even be decided to move this section into the (now following) §15.15 Iterators and Async Streams clause.
IAsyncEnumerator<T> GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default); | ||
``` | ||
|
||
> However, as we have not mentioned type `System.Threading.CancellationToken` in any other threading contexts in the spec, it has been omitted from the requirements, as if there was no argument expected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s do it in this PR
@@ -5416,3 +5418,52 @@ When the body of the async function terminates, the return task is moved out of | |||
If the return type of the async function is `void`, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread’s ***synchronization context***. The exact definition of synchronization context is implementation-dependent, but is a representation of “where” the current thread is running. The synchronization context is notified when evaluation of a `void`-returning async function commences, completes successfully, or causes an uncaught exception to be thrown. | |||
|
|||
This allows the context to keep track of how many `void`-returning async functions are running under it, and to decide how to propagate exceptions coming out of them. | |||
|
|||
### §asynchronous-streams Asynchronous stream creation and usage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this section at all? (Really not needed in normative text?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(All of the bits in the bullet points can be included in the iterator section.)
A method that creates an asynchronous stream has the following characteristics: | ||
|
||
- It's declared with the `async` modifier. | ||
- It returns an `IAsyncEnumerable<T>`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or IAsyncEnumerator<T>
.
Summary of discussions:
|
It looks like there's still quite a bit to do here - but it's not clear to me that Rex is the right person to do this. For discussion at today's call (if there's time): who should take this on? (I may have time, if we decide I'm the most appropriate person bearing in mind other PRs...) |
@@ -5213,41 +5215,41 @@ When a function member is implemented using an iterator block, it is a compile-t | |||
|
|||
### 15.14.2 Enumerator interfaces | |||
|
|||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>`. For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>`, respectively. | |||
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interface `System.Collections.Generic.IEnumerator<T>` (`System.Collections.Generic.IAsyncEnumerator<T>` for async streams). For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator` and `IEnumerator<T>` (`IAsyncEnumerator<T>` for async streams), respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think we should continue to use the term "async stream". It fits well with the feature, and "stream" is the conventional term.
I also like the idea of some reordering. That may be a follow-up PR?
|
||
- It's declared with the `async` modifier. | ||
- It returns an `IAsyncEnumerable<T>`. | ||
- It contains a `yield return` statement to return successive elements in the asynchronous stream. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add that the argument to yield return
must be implicitly convertible to the type argument for the return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't combine this section with the enumerator section, we should add that yield break
is can be used.
> | ||
> *end example* | ||
|
||
Consumption of an asynchronous stream requires an `await foreach` to enumerate the elements of the stream. Adding the `await` keyword requires the method that enumerates the asynchronous stream to be declared with the `async` modifier and to return a type allowed for an async method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More later, but I think a good way to address this is to look at the statements section, and add a new subclause for "The await foreach" statement, rather than modifying the foreach statement section.
> | ||
> *end example* | ||
|
||
A method can both consume and produce an asynchronous stream. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's needed.
@@ -1094,12 +1094,14 @@ The end point of a `for` statement is reachable if at least one of the following | |||
|
|||
### 13.9.5 The foreach statement | |||
|
|||
(Almost all of the text in this subclause was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make this two sub-clauses: One for "foreach", and one for "await foreach".
The grammar is similar, and I'll defer to Nigel on if one or two grammar productions makes more sense.
The section on "MoveNext" and "MoveNextAsync" becomes more clear as two distinct sections: One for a synchronous "move next" and one for an asynchronous value-task based "move next asynchronously"
@@ -1488,6 +1519,8 @@ While a mutual-exclusion lock is held, code executing in the same execution thre | |||
|
|||
## 12.14 The using statement | |||
|
|||
(Almost all of the text in this subclause was written prior to the addition of async streams; the text applies to async streams as well, unless stated otherwise, usually in parenthetic text. Asynchronous stream creation and usage is discussed in §asynchronous-streams.) | |||
|
|||
The `using` statement obtains one or more resources, executes a statement, and then disposes of the resource. | |||
|
|||
```ANTLR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is in #672
There is some overlap between this PR and PR #672 in 13.14, "The using statement" that will require reconciliation when the second of these two PRs is merged. (PR 606 was spec'd first.)