112 lines
3.2 KiB
Markdown
112 lines
3.2 KiB
Markdown
|
|
# C# LDM notes for Dec 12, 2018
|
|
|
|
## Agenda
|
|
|
|
1. Open issues with async streams
|
|
2. Pattern dispose
|
|
|
|
## Discussion
|
|
|
|
### Cancellation in GetAsyncEnumerator
|
|
|
|
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.
|
|
|
|
```C#
|
|
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.
|
|
|
|
### Pattern binding
|
|
|
|
Which pattern do we want the compiler to recognize for GetAsyncEnumerator?
|
|
|
|
Binding options for some expression target of a `foreach`, `e`:
|
|
|
|
1. `e.GetAsyncEnumerable()`
|
|
2. `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.
|
|
|
|
### Recognizing an iterator
|
|
|
|
Consider the following:
|
|
|
|
```C#
|
|
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:
|
|
|
|
1. Add a `yield` in the body.
|
|
2. Remove the `async`.
|
|
|
|
**Conclusion**
|
|
|
|
Add an error, undecided on the error message.
|
|
|
|
### When we start recognizing pattern Dispose, do we call it in foreach?
|
|
|
|
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:
|
|
|
|
1. A pattern enumerable type did not implement IDisposable, but had a
|
|
Dispose method. This method would now be call.
|
|
2. 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. |