- Open issues with async streams
- Pattern dispose
We previously decided that GetAsyncEnumerator takes a CancellationToken. When do we check if it's been cancelled?
Our design for cancellation in the user code itself is to have the user write the core logic for their IAsyncEnumerable using an IAsyncEnumerator iterator method. The user can manually check for cancellation inside the iterator body.
class C : IAsyncEnumerable
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token)
{
...
token.ThrowIfCancelled();
...
}
}
Q: Do we want to generate any checks manually? One option is to check the cancellation token on every MoveNext call.
This comes down to: where is it most useful to request cancellation?
The most important place to include the cancellation token is in the await calls themselves. This isn't compiler generated code at all -- if the expressions being awaited need a CancellationToken the user will need to pass it themselves. Other places, like every entry to MoveNextAsync, will be called regularly, but will only be able to cancel synchronous code, instead of the long-running async operation.
Conclusion
The compiler will not generate checks for cancellation for now. If we get user feedback that it's important, we will revisit this decision.
Which pattern do we want the compiler to recognize for GetAsyncEnumerator?
Binding options for some expression target of a foreach
, e
:
e.GetAsyncEnumerable()
e.GetAsyncEnumerable(default(CancellationToken))
One of the primary drawbacks for (1) is that all users must make their CancellationToken parameters optional. This may be a problem for implementors who consider it very important to have a CancellationToken.
Conclusion
We will attempt to bind the call
e.GetAsyncEnumerable(default(CancellationToken))
and if it succeeds and has
a conforming return type, the pattern binds successfully.
Consider the following:
class Program
{
static async IAsyncEnumerable<string> M()
{
throw new NotImplementedException();
}
}
As currently designed, this is illegal. async
methods must either return
a Task-like type or return IAsyncEnumerable/IAsyncEnumerator
and contain
a yield statement. This is neither.
The proposal is to produce an error. There are a number of fixes:
- Add a
yield
in the body. - Remove the
async
.
Conclusion
Add an error, undecided on the error message.
Certainly, it seems very important for the feature. Since a ref struct cannot implement interfaces, pattern Dispose is the only way to implement disposal.
There are a number of possible breaking changes:
- A pattern enumerable type did not implement IDisposable, but had a Dispose method. This method would now be call.
- If there are two Dispose extension methods, that will now produce an ambiguity and thus a compilation error.
Conclusion
Let's narrow the pattern-based Dispose change down to ref structs only. This means every other type will be required to implement IDisposable.