Comments (1)
in observable factory delegate cancellationToken gets canceled not at the moment of destroying subscription
The cancellation token's purpose is not to tell you that the subscription has ended (although under certain circumstances, unsubscription will trigger the thing that it's actually telling you about). Its purpose is to tell the callback that the result the callback was meant to produce is no longer required. This is related to the subscription only insofar as if someone unsubscribes before your callback returns, cancellation may occur. But there are no guarantees at all about what that token does after your callback returns.
Arguably we have a documentation bug here, because where the docs say that these tokens allow:
cancellation at any stage of the resource acquisition or usage
it is at best unclear, and possibly misleading, to say "usage" here. You could reasonably interpret the resource as being "used" for the entire lifetime of the subscription. I think this should really have said:
cancellation at any stage of resource acquisition, or of the creating a new
IObservable<TResult>>
from that resource
So we use the resource to create an observable. (That's the cancellable part of "using" the resource.) And then we go on to use the resulting IObservable<TResult>>
. (And for that stage, it's the call to Dispose
that tells us when it's all over, not the cancellation token.)
The design intent is that the cancellation token's state is only meaningful for as long as the callback is running. Once the callback has returned, there's no longer any work left to cancel.
Why am I saying that's the design intent?
- there's already a separate mechanism for dealing with this longer type of "usage" (the call to
Dispose
) - looking at the code, it's clear that these cancellation tokens are only intended to have any meaning for as long as the callbacks are in progress
- the unit tests are consistent with this interpretation
So in your example, I wouldn't have expected the cancellation tokens to be signalled at all. However, as the documentation says, this method supports:
best-effort cancellation
So that means two things:
- no guarantees about when cancellation will occur
- no guarantees about whether or not the token will be cancelled
Since cancellation is described as 'best effort' here, the guarantees are very weak. The only thing that is guaranteed is that each cancellation token won't be signalled while the callback is running if the result of the callback is still required.
There's definitely something a bit peculiar about the behaviour this example produces. I would not have expected the cancellation token to get signalled when nothing needed to be cancelled. (And both of your callbacks run to completion, and the subscription continue to run for some time after that. It's definitely too late to cancel a callback if that callback already returned!) But since the behaviour of the token after it has served its useful purpose is, strictly speaking, undefined, technically it is not a bug the token may be signalled long after it's useful, and that's what you're actually seeing here.
I just want to clarify why I'm saying it's occurring after it's useful (i.e., later than we might reasonably expect), when you're reporting that it's coming earlier than you think it should.
The behaviour you describe as expected behaviour:
Created resource
Init resource observable
Resource factory cancellation called
Subscribed
Next:-1
Next:0
Next:1
Next:2
Next:3
Resource observable cancellation called
Unsubscribed
Disposed resource
is also not what I would have expected here.
Here's the output I was expecting:
Created resource
Init resource observable
Subscribed
Next:-1
Next:0
Next:1
Next:2
Next:3
Unsubscribed
Disposed resource
I.e., I wasn't expecting either of the cancellation tokens to get cancelled, because both of the callbacks ran to completion (meaning that there was no work left to cancel).
If you interpret the documentation ("any stage of the resource acquisition or usage") to extend to the full lifetime of the subscription, your interpretation makes sense. But this has never been what the implementation was actually trying to achieve. So that's why I'm classifying this as a documentation bug.
If you want to know when the resource is no longer in use, the call to Dispose
tells you that.
I think it would be cleaner if they never got cancelled in this situation. But there's every chance that code out there somewhere is relying on the fact that they do get cancelled after the work they were meant to govern completes, so I think we're kind of stuck with that behaviour.
So in conclusion:
- if I were to modify the behaviour here, it would be to prevent these cancellation tokens from getting cancelled at all in this particular scenario (i.e., neither the behaviour you observed, nor the behaviour you said you expected)
- the current behaviour is odd but not outright wrong, and it's possible that some code relies on the current behaviour, so changing it could be counterproductive
- the documentation for this method needs to be more clear about what these cancellation tokens are actually for
from reactive.
Related Issues (20)
- Add BackOffRetry example in IntroToRx
- Fix sentence on IntroToRx book
- [Feature] GroupSequentialBy
- PageNotFoundError about https://www.reactiveui.net/slack HOT 2
- Referencing System.Reactive from a WinUI 3 app or library is causing XLS errors to display in VS while XAML files are open HOT 1
- Possible Deadlock in BehaviorSubject.Subscribe HOT 2
- Incorporate https://github.com/runceel/ReactiveProperty HOT 5
- Throttle breaks the observable chain while running on wasm in .net 8 HOT 5
- Window and Buffer operators omit items which are released immediately HOT 5
- Benchmarks don't run for .NET 6+
- TestScheduler WithAsync swallows all exceptions making all tests pass HOT 3
- Publish symbols for assemblies in this package HOT 1
- Union doesn't dispose enumerator for empty lists HOT 1
- IntroToRx recommends wrong Window overload
- Fully update Ix for .NET SDK 8.0 HOT 1
- Bug in Merge for USE_FAIR_AND_CHEAPER_MERGE HOT 4
- ToAsyncEnumerable_Observable_Cancel_InFlight sometimes fails spuriously
- Do operator can perform unexpected type conversion on observable sequence HOT 2
- Feature Request: Make `AsyncGate.LockAsync` cancellable HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from reactive.