Could we constrain the T to awaitable? ie IAsyncEnumerable<async T>
This would be a language based constraint. Since async and await are pattern based, the compiler would check the applied T to see if it matches that pattern. In metadata is would be supported via an attribute.
interface IAsyncIEnumerator<async T>
{
.GetEnumerator() : IAsync<IAsyncEnumerator<T>>
}
interface IAsyncEnumerator<async T>
{
.MoveNext() : IAsync<Bool>
.Current : IAsync<T>
}
IAsync is just used to indicate that the result is awaitable.
Since the compiler uses duck typing for this purpose, this constraint will also fall into that category.
I don't see the point. Such an interface could just define their members to return Task<T> or, eventually, ValueTask<T>.
The awaiter pattern is pretty loose and can be handled through an extension method which would make it impossible to emit common IL.
@HaloFour The current IEnumerable<T> / IEnumerator<T> doesn't permit awaiting on the GetEnumerator or the MoveNext.
interface IEnumerable<T>
{
.GetEnumerator() : IEnumerator<T>
}
interface IEnumerator<T>
{
.MoveNext() : Boolean
.Current : T
.Reset()
}
Also wasn't @ljw1004 implementing it that it isn't restricted to just Task<T> or ValueTask<T>, allowing any type that followed the awaitable pattern?
@AdamSpeight2008
The current
IEnumerable<T>/IEnumerator<T>doesn't permit awaiting on theGetEnumeratoror theMoveNext.
This proposal wouldn't change that.
Also wasn't @ljw1004 implementing it that it isn't restricted to just
Task<T>orValueTask<T>, allowing any type that followed the awaitable pattern?
He's implementing asynchronous sequences so that IAsyncEnumerable<T> isn't required as long as the enumerated follows the pattern. As such generic constraints aren't at all necessary since you aren't required to use IAsyncEnumerable<T> or make it work with other awaitables.
Stepping back,
_Is there value in letting someone write a general-purpose combinator which operators upon arbitrary awaitable things?_
There hasn't been much need for it so far, since we all use Task everywhere. We also use the ConfiguredTaskAwaitable type but there's not much sense in writing a combinator over that. Some people make other things awaitable, e.g. a button-click or an animation, but that seems kind of niche.
With C#7 it will become more common to use one other kind of task, ValueTask. And so yes we will want to write combinators such as Task.WhenAll which can operate over both Task and ValueTask equally.
One oddity is that, even if we can write Task.WhenAll which operates upon both Task and ValueTask equally, _it has to commit to a concrete return type_. You wouldn't be able to write this:
T WhenAll<T>(params T[] args) where T : awaitable
The only way you could do this would be by adding an extra constraint that T must both follow the _awaiter_ pattern and also the _tasklike_ pattern.
I see these ways that you could write combinators:
1: T WhenAll<T>(T arg1, T arg2) where T : awaitable, tasklike
2: WhenAll(valueTask1, task2) // rely on implicit conversion from ValueTask to Task or vice versa
3: WhenAll(valueTask1.AsTask(), task2) // rely on an explicit conversion
Solving this problem at language level with [1] would be premature right now. We should see how much it's needed, and how well it's solved at the library level with [2,3]. If it is needed a lot, and if both of those approaches seem just too heavyweight, that's the time to jump in with a language feature. Not before!
@ljw1004
Some people make other things awaitable, e.g. a button-click or an animation, but that seems kind of niche.
I am pretty sure it's a common practice in both Xamarin and WPF these days. If people are async-aware they do it end-to-end.
I wonder if this could be achieved by types classes (https://github.com/dotnet/csharplang/issues/110) once they are implemented.
@ymassad
Relevant conversation: https://github.com/dotnet/csharplang/issues/1454#issuecomment-380534923
This feature would allow writing of orchestrating code irrelevant of the type of the awaitable.
As a concrete example, I have created a custom awaitable (and tasklike) called DfTask to present a operation in a producer-consumer dataflow. You can find the project here: https://github.com/ymassad/ProceduralDataflow
With the proposed feature I could write generic shared code describing the flow of the processing of data, and then use it both in cases where I need simply asynchrony (individual steps in processing are simple asynchronous operations) or when I need to implement the producer-consumer pattern.
As @ljw1004 said, we would need both awaitable and tasklike constraints to do this.
Most helpful comment
Stepping back,
_Is there value in letting someone write a general-purpose combinator which operators upon arbitrary awaitable things?_
There hasn't been much need for it so far, since we all use Task everywhere. We also use the ConfiguredTaskAwaitable type but there's not much sense in writing a combinator over that. Some people make other things awaitable, e.g. a button-click or an animation, but that seems kind of niche.
With C#7 it will become more common to use one other kind of task, ValueTask. And so yes we will want to write combinators such as Task.WhenAll which can operate over both Task and ValueTask equally.
One oddity is that, even if we can write Task.WhenAll which operates upon both Task and ValueTask equally, _it has to commit to a concrete return type_. You wouldn't be able to write this:
The only way you could do this would be by adding an extra constraint that T must both follow the _awaiter_ pattern and also the _tasklike_ pattern.
I see these ways that you could write combinators:
Solving this problem at language level with [1] would be premature right now. We should see how much it's needed, and how well it's solved at the library level with [2,3]. If it is needed a lot, and if both of those approaches seem just too heavyweight, that's the time to jump in with a language feature. Not before!