[feature request] Implement ToObservable for CancellationToken
- Dominant language
- C#
- Stars
- 7.2k
- Forks
- 798
- PR merge metrics
- No merged PRs in 30d
Description
#### Feature request
A function to convert a `CancellationToken` token into an observable that emit `OnError` with `OperationCanceledException` when the token is signaled. It can possibly be implemented like this:
```c#
public static class RxExt
{
public static IObservable ToObservable(this CancellationToken @this)
{
if (@this.IsCancellationRequested)
{
return Observable.Throw(new OperationCanceledException(@this));
}
if (!@this.CanBeCanceled)
{
return Observable.Never();
}
return Observable.Create(
observer => @this.Register(
(_, token) => observer.OnError(new OperationCanceledException(token)),
null));
}
}
```
> Which next library version (i.e., patch, minor or major)?
Patch or Minor
> What are the platform(s), environment(s) and related component version(s)?
All supported platforms
> How commonly is this feature needed (one project, several projects, company-wide, global)?
Company-wide
> Please describe the feature.
Here's one possible use case, coupled with `TakeUntil` (I'm sure there are many others). Here, the `Finally` action is called upon the first item emit or when the token is signaled.
This code here is a bit contrived (I might as well use [`IObservable.ToTask`](https://docs.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229587(v=vs.103)) or `IObservable.RunAsync`) but in our real-life Rx piple-lines this use case does makes sense:
```C#
IDisposable RunUntilCancelled(IObservable sequence, Action action, CancellationToken token)
{
return sequence
.FirstAsync()
.TakeUntil(token.ToObservable())
.Finally(action)
.Publish()
.Connect();
}
async Task test_CancellationToken_ToObservable()
{
var sequence = Observable
.Interval(TimeSpan.FromMilliseconds(1000))
.Do(n => Console.WriteLine(n))
.Skip(3);
var sw = Stopwatch.StartNew();
var tcs = new TaskCompletionSource();
using var cts = new CancellationTokenSource(2500);
using var subscription = RunUntilCancelled(sequence, tcs.SetResult, cts.Token);
await tcs.Task;
var lapse = sw.ElapsedMilliseconds;
Trace.Assert(lapse >= 2500 && lapse < 3000);
}
```
**Updated**, the initial version was rather naive, [a race condition has emerged](https://stackoverflow.com/a/72496607/1768303) since I stated using it. Here is an updated version aiming to solve it, and also giving a choice whether to use `OnError` or `OnNext`:
```c#
public static class RxExt
{
public static IObservable ToObservable(
this CancellationToken @this,
bool useOnError = true)
{
if (@this.IsCancellationRequested)
{
return useOnError ?
Observable.Throw(new OperationCanceledException(@this)) :
Observable.Return(@this);
}
if (!@this.CanBeCanceled)
{
return Observable.Never();
}
return Observable.Create(
observer =>
{
int disposed = 0;
bool IsListening() => Interlocked.CompareExchange(ref disposed, 0, 0) == 0;
var rego = @this.Register((_, token) =>
{
if (useOnError)
{
if (IsListening())
{
observer.OnError(new OperationCanceledException(token));
}
}
else
{
if (IsListening())
{
observer.OnNext(token);
}
if (IsListening())
{
observer.OnCompleted();
}
}
}, state: null);
return Disposable.Create(() =>
{
if (Interlocked.CompareExchange(ref disposed, 1, 0) == 0)
{
rego.Unregister();
}
});
});
}
}
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.