Repository navigation
Conversation
…ng them EditDiff on an IObservable<IEnumerable<TObject>> now forwards source errors to the subscriber. The subscriber is connected to the cache before the source is subscribed, so changes, failure and completion arrive in order. The existing terminal-event tests now subscribe to the result of EditDiff rather than to its source. Part of #1142.
… them The Task overloads of ObservableChangeSet.Create, cache and list, now behave like their Observable.Create analogs. A failing task's error arrives on the thread that fails it rather than later on the thread pool, the overloads that return no clean-up complete when their task completes, and the completion-on-dispose that Rx always dropped is gone. Public signatures are unchanged. EditDiff goes back to ObservableChangeSet.Create, using the Task overload with ForEachAsync so the task's outcome is the source's. Part of #1142.
The overloads whose task returns nothing are now Observable.Using over the collection, with the task observed by FromAsync and ending the collection's changes through TakeUntil. Publish starts the task only once the changes are subscribed, so a task that finishes during subscription keeps its changes. This replaces the hand-wired subscriptions of the previous commit. The overloads that return an IDisposable or Action are back to main's implementation. They already sit on Rx's Observable.Create(Task<IDisposable>), which disposes late clean-up itself. Part of #1142.
copilot-instructions.md is the only instruction file that is always in context, and it presented Observable.Create as the operator pattern while rx.instructions.md says composition comes first. It now carries a short version of that rule, the smells that mean a composition was missed, and a pointer to read rx.instructions.md before writing any Observable.Create or ObservableChangeSet.Create body.
ViktorJannicke
added a commit
to Runic-Artifex/DynamicData
that referenced
this pull request
Oct 20, 2026
Deliver EditDiff source errors; task-backed Create completes with its task (port upstream reactivemarbles#1206)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1142.
EditDiffon anIObservable<IEnumerable<TObject>>used the synchronousObservableChangeSet.Create, whose callback only gets anISourceCacheand so has no way to fail. A source error after subscription was rethrown by Rx on the thread that delivered it.The
ObservableChangeSet.Createoverloads whose task returns nothing can fail and complete through that task, soEditDiffnow uses one, withForEachAsyncmaking the source's lifetime the task:Those overloads did not behave like their analog,
Observable.Create(Func<IObserver<T>, CancellationToken, Task>). They never completed when the task did, and because they awaited inside anasynclambda, a task that ended on a thread with aSynchronizationContext(a UI thread) had its outcome delivered later, on the thread pool.They are now composed from Rx operators:
Usingowns the collection.FromAsyncobserves the task onImmediateScheduler, so its outcome arrives on the thread that completes it. Unsubscribing cancels the token.TakeUntilends the changes when the task succeeds, and fails them when it fails. It holds no lock while delivering.Publishstarts the task only onceTakeUntilis subscribed to the changes, so a task that finishes during subscription keeps its changes.The overloads whose task returns an
IDisposableorActionare unchanged. They already sit on Rx'sObservable.Create(Func<IObserver<T>, CancellationToken, Task<IDisposable>>), which is their analog and disposes clean-up that arrives after unsubscribing. No public signature changes.Instructions
copilot-instructions.mdis the only instruction file that is always in context, and its operator pattern presentedObservable.Createas the way operators are built, whilerx.instructions.mdsays composition comes first. It now carries a short version of that rule: compose with existing operators, useObservableChangeSet.Createrather than routing around it, and the smells that mean a composition was missed. It also says to readrx.instructions.mdbefore writing anyObservable.CreateorObservableChangeSet.Createbody.Tests
Cache/ObservableChangeSetFixture:TaskCompletesTheStreamAfterItsChangesTaskFailureIsDeliveredOnTheThreadThatFailsItTaskThatFinishesDuringSubscriptionStillDeliversItsChangesDisposingTheSubscriptionCancelsTheTaskList/CreationFixtures:TaskCompletesTheStreamAfterItsChangesTaskFailureIsDeliveredOnTheThreadThatFailsItTaskThatFinishesDuringSubscriptionStillDeliversItsChangesSix of these fail on
main.DisposingTheSubscriptionCancelsTheTaskpins behaviour that is kept.Cache/EditDiffChangeSetFixture:ResultFailsWhenSourceFailsAfterSubscription(new) fails onmain.ResultCompletesIfAndOnlyIfSourceCompletesandResultFailsIfAndOnlyIfSourceFailssubscribed to the source rather than to the result ofEditDiff, so they never exercised the operator. Both now do.Notes
Behavioural changes to public APIs.
mainis on10.0-preview.ObservableChangeSet.Createwith aFunc<..., Task>now completes when the task completes, and a failing task's error arrives on the thread that fails it.Func<ISourceList<T>, CancellationToken, Task>overload now connects before running the task, like the cache one, so changes made during the task arrive as they happen instead of being folded into one changeset.EditDiff: an error after subscription now arrives asOnError. A source that emits more than once while being subscribed to now produces a changeset per emission, and changes ahead of a failure during subscription are delivered before the error.