-
Notifications
You must be signed in to change notification settings - Fork 754
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
System.Interactive.Async with C# 8.0 proposed interfaces #423
Conversation
For some of the inner-loop async things, like in the aggregators, should it be |
I've spent a few days to refresh "Interactive Extensions" (Ix) for In a nutshell, this work enables "async LINQ to Objects" and has all of the Standard Query Operators available. To download the NuGet packages, follow the link to the AppVeyor build, where you can download the latest NuGet packages from the "Artifacts" tab. A summary of the work done:
Open questions and topics for discussion:
|
Close & defer until C# 8? |
This is the reference impl for C# 8. |
This is a huge PR and even if we could review it, I don't see any indication the feedback could be addressed prior to merging this. Also I'm not following the C# 8 spec but is this based on the most recent design? |
I suggest closing this PR for now. The code is still in the |
Good with me as long as the branch will survive. It's probably been abandoned anyway. |
874b5cd
to
f24aa5d
Compare
I have some changes to this branch but no longer have permission to push. |
@bartdesmet Sorry, must have gotten lost in the move to this org, will fix now |
Should be fixed now, your'e an admin here. |
🎉 to the change on the get enumerator call |
Latest commit to experiment with
are doable based on the experiments in this branch. Once the interfaces are finalized, we'll proceed accordingly. For discussions on the interface design, please join on https://github.com/dotnet/corefx/issues/32640. |
How should we proceed with this branch? Start reviewing the unit tests? Post fixes/improvements to the tests/operators to this branch?
|
Quick review:
|
Would it be possible if the Ix.Async related code be moved into its standalone solution? |
We need to revert the two commits that removed the reference assemblies and REFERENCE_ASSEMBLY defines. The issue is that we need to expose different surface area for netstandard2.0, netcoreapp2.1, and netstandard2.1 because of TakeLast, etc. We need the impl to always be there in the lib or we'll create runtime errors for people. We just can't expose those methods. ProduceReferenceAssembly isn't the hard part, it's the different surface area and creating the correct NuGet package, both of which were handled by the define and Extras. |
Please don't make code style changes in this branch. It'll create harder to review merges and should be a seperate PR for discussion and review. |
@bartdesmet I added a property I also added the .NET Core |
…nto IxAsyncCSharp8
…nto IxAsyncCSharp8
Is the big rename really necessary? In my eyes method names like var result = seq.OrderBy(async x => x.GetNameAsync()); I doubt that there are many cases where you actually want the non-awaited variant. So wouldn't it be better to treat the exception special than to make it the default? If one really wants a sequence of tasks he could then do something like that: var list = await seq.RawSelect(s => new ValueTask(s)).ToListAsync();
// or
await Task.WhenAny(seq.RawSelect(async x => await x.GetNameAsync()).ToArrayAsync()); |
…f on .NET Standard 2.1
I do fully agree with the sentiment on the ugliness of names and this is by no means final; we may totally go back to the original state once we figure out the scope of potential issues. For the preview we're about to release, we wanted to err on the safe side and have all existing LINQ operators be 1:1 identical across After this preview, we will evaluate the pros and cons of disambiguation by name. An initial gut feeling of running the whole API by some corefx/language folks was to disambiguate operators with async selectors, predicates, etc. using an We should have the discussion at length after getting an initial preview out, but some factors to consider are the following:
Just drilling a tiny bit deeper into the LINQ query expression binding, consider the following query: from x in xs
from y in ys
select e which desugars into xs.SelectMany(x => ys, (x, y) => e)
xs.SelectMany(x => ys, (x, y) => e)
xs.SelectMany(async x => ys, (x, y) => e)
xs.SelectMany(x => ys, async (x, y) => e)
xs.SelectMany(async x => ys, async (x, y) => e) I'll ignore the syntactic aspects of query expressions that containing
Either way, let's assume a query clause can be asynchronous, and we have to lower and bind it by generating
One extreme is to support all overloads but it quickly leads to insanity. Although not all of them need to be implemented from scratch by a library; a valid implementation could just have "all sync" and "all async" master overloads, and the "one or more async" overloads could delegate into the "all sync" one by wrapping the result of the sync delegates into static IAsyncEnumerable<R> SelectMany<T, C, R>(this IAsyncEnumerable<T> source, Func<T, IAsyncEnumerable<C>> collectionSelector, Func<T, C, R> resultSelector) => ...;
static IAsyncEnumerable<R> SelectMany<T, C, R>(this IAsyncEnumerable<T> source, Func<T, ValueTask<IAsyncEnumerable<C>>> collectionSelector, Func<T, C, ValueTask<R>> resultSelector) => ...;
static IAsyncEnumerable<R> SelectMany<T, C, R>(this IAsyncEnumerable<T> source, Func<T, ValueTask<IAsyncEnumerable<C>>> collectionSelector, Func<T, C, R> resultSelector) =>
SelectMany<T, C, R>(source, collectionSelector, (x, y) => new ValueTask<R>(resultSelector(x, y)));
static IAsyncEnumerable<R> SelectMany<T, C, R>(this IAsyncEnumerable<T> source, Func<T, IAsyncEnumerable<C>> collectionSelector, Func<T, C, ValueTask<R>> resultSelector) =>
SelectMany<T, C, R>(source, x => new ValueTask<IAsyncEnumerable<C>>(collectionSelector(x)), resultSelector); This would be the easiest story for the language to lower and bind. Same methods names as the original ones, just decide to lower by using zero or more async lambdas, and see what binding looks like. Thus, the library is responsible to provide all combinations, which intersects with the space of possible overload resolution conflicts. It further bleeds into a discussion around:
Other options of async in query expressions include:
Either way, I think the whole overload space with async delegates and/or cancelable async delegates warrants input from the .NET corefx team, the C# LDM team, library users (such as Entity Framework), and end users. It may also be the source of API design guidelines for methods with delegate parameters that want to support "deep" asynchrony with or without cancellation, similar to the "Task-based Asynchronous Pattern (TAP)". For now, we'll freeze the API surface for a first preview drop of |
…ned to evaluate design options using a single SUPPORT_FLAT_ASYNC_API flag.
In order to evaluate both API options to support async and "deep cancellation", I've centralized all of these overloads into a single place for corefx and LDM folks to review. See As discussed above, the preview will ship with Anyone can build the "flat" world to play around with it as well to identify possible issues, but note that the test code current fails to build with that option turned on, revealing some of the places of concern where lambda parameters can't be inferred. If I get some time in the next few weeks, I'll continue to clean up the test code and add a corpus of "asyncified" queries, originally expressed against |
Amazing! 🎉 I can't wait to have a play about with this 😃 |
The CI feed has it now, just awaiting a few sanity checks before publishing to nuget. |
I've also created an early prototype of async query expressions in C# over at https://github.com/bartdesmet/roslyn/blob/AsyncLinq. This enables Some docs can be found here, also summarizing some of the discussion we've been having here. |
|
||
Core(); | ||
|
||
// REVIEW: Safety of concurrent dispose operation; fire-and-forget nature of dispose? |
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.
@bartdesmet Upon playing a bit with async iterators in conjunction with Rx, I found that the Ix-Rx-bridging operator ToObservable
does not play nicely with how Roslyn lowers async iterators. Specifically, DisposeAsync may not be called in-flight and will throw.
Whether the NotSupportedException
shouldn't rather be an InvalidOperationException
is not the subject of this review, however, it is a breaking change to how the "classic" Ix.Async implementation would handle in-flight disposal (it would do a best-effort approach to cancel MoveNext
).
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 had to trampoline the async Dispose with the async MoveNext in my implementation. It was painful for my performance-sensitive eyes.
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.
But Dispose doesn't cancel in-flight MoveNextAsync
, does it?
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 on Dispose
, the cancellation token source should be canceled. That is probably why there is a cancellation token source at all.
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 afraid the compiler that implements the async iterator doesn't put a CancellationTokenSource into it...if I didn't miss anything.
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's right. And it's also right that the AsyncDispose
at that position is a contract violation.
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 is, and also the fact that it is thrown synchronously caught me at one point when playing around.
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.
Clarification: Roslyn, at least on the current master (porbably not in any stable release of VS) does indeed create a CancellationTokenSource
if the signature for the async iterator contains a CancellationToken
parameter. If created in every case, it could help fix the Dispose-issue.
@jcouv Would you be so kind to weigh into this conversation?
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.
DisposeAsync
on a generate async-iterator does two things: it executes any finally
blocks from the current yield
statement, and it disposes the CancellationTokenSource
(if any).
From the design notes:
The caller of an async-iterator method should only call DisposeAsync() when the method completed or was suspended by a yield return.
LDM did briefly discuss allowing DisposeAsync()
to be called any time, as a way to cancel an iteration, but this would require some synchronization which we felt was too much overhead.
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 guess it's then up to System.Linq.Async / System.Interactive.Async to work around that. Otherwise virtually any Rx-operator-chain may throw on unsubscription if there's a conversion from an async iterator to an IObservable somewhere in between.
This PR is not intended to be merged into the
develop
ormaster
branch at this point. It's used as a playground to see the changes that'd be needed toSystem.Interactive.Async
to support LINQ operators using the interface definitions proposed for C# 8.0 up to the 8/30.2017 C# language design meeting.See dotnet/csharplang#866 for the PR containing these interface definitions, merged into Async Streams.
The initial iteration deals with all the interface changes, and gets us to a compiling solution, without deep consideration of breaking changes compared to the existing assembly. Tests have to be reviewed. We can use this PR to discuss pros and cons of the current interface design.
Update 10/5/18: Supports latest interface definitions as per https://github.com/dotnet/corefx/issues/32640
Update 11/28/18: Supports latest interface definitions as per https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-28.md