Take this little script (designed in LINQPad but should run everywhere):
void Main()
{
Task.Run(() => Worker()).Wait();
}
async Task Worker()
{
if (SynchronizationContext.Current != null)
throw new InvalidOperationException("Don't want any synchronization!");
BaseClass provider = new Implementation();
Func<IObserver<TimeSpan>, CancellationToken, Task> subscribeAsync =
provider.CreateValues;
var observable = Observable.Create(subscribeAsync);
var cancellation = new CancellationTokenSource(5500).Token; // gets cancelled after 5.5s
cancellation.Register(() => Console.WriteLine("token is cancelled now"));
await observable
.Do(ts =>
{
Console.WriteLine("Elapsed: {0}; cancelled: {1}",
ts,
cancellation.IsCancellationRequested);
cancellation.ThrowIfCancellationRequested();
})
.ToTask(cancellation)
.ConfigureAwait(false);
}
abstract class BaseClass
{
// allow implementers to use async-await
public abstract Task CreateValues(IObserver<TimeSpan> observer, CancellationToken cancellation);
}
class Implementation : BaseClass
{
// creates Values for 10s; entirely CPU-bound: no way for async-await hence return Task.CompletedTask
public override Task CreateValues(IObserver<TimeSpan> observer, CancellationToken cancellation)
{
try
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine("{0}/{1} cancelled:{2}", i, j, cancellation.IsCancellationRequested);
Thread.Sleep(333);
}
if (cancellation.IsCancellationRequested) // !! never gets true !!
throw new ApplicationException("token is cancelled");
observer.OnNext(sw.Elapsed);
}
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
}
The method Implementation.CreateValues
justs keeps running for the entire 10 seconds instead of stopping after 5.5s. The CancellationToken
passed in by Observable.Create
doesn't even transition to a cancelled state (the original token of course does)!
Is it a bug? Is it my fault by doing something wrong?
Output is:
0/0 cancelled:False
0/1 cancelled:False
0/2 cancelled:False
Elapsed: 00:00:01.0205951; cancelled: False
1/0 cancelled:False
1/1 cancelled:False
1/2 cancelled:False
Elapsed: 00:00:02.0253279; cancelled: False
2/0 cancelled:False
2/1 cancelled:False
2/2 cancelled:False
Elapsed: 00:00:03.0274035; cancelled: False
3/0 cancelled:False
3/1 cancelled:False
3/2 cancelled:False
Elapsed: 00:00:04.0294796; cancelled: False
4/0 cancelled:False
4/1 cancelled:False
4/2 cancelled:False
Elapsed: 00:00:05.0315332; cancelled: False
5/0 cancelled:False
5/1 cancelled:False
token is cancelled now
5/2 cancelled:False
Elapsed: 00:00:06.0335601; cancelled: True
6/0 cancelled:False
6/1 cancelled:False
6/2 cancelled:False
Elapsed: 00:00:07.0436211; cancelled: True
7/0 cancelled:False
7/1 cancelled:False
7/2 cancelled:False
Elapsed: 00:00:08.0457921; cancelled: True
8/0 cancelled:False
8/1 cancelled:False
8/2 cancelled:False
Elapsed: 00:00:09.0477509; cancelled: True
9/0 cancelled:False
9/1 cancelled:False
9/2 cancelled:False
Elapsed: 00:00:10.0498751; cancelled: True
[AggregateException] at Main/Task.Wait()
The cancellation token getting passed to the subscribeAsync
function is instantiated by the Observable.Create
call and is not the cancellation token you're instantiating.
As per the Observable.Create
overload summary:
Creates an observable sequence from a specified cancellable asynchronous Subscribe method. The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation.
In short, the cancellation token will get cancelled when you dispose of the subscription, not after the specified delay.
You should be able to refactor your code as follows to make it work:
Observable.Create(observer => subscribeAsync(observer, cancellation));
Hope it helps.
This is not really an answer to the question but a rewrite of the sample code using System.Threading.Tasks.Dataflow inplace of System.Reactive (far too much code for being posted as a comment):
This has several advantages:
observer
parameter is now a Task
every implementation has something to await
for. Do()
(now in ActionBlock
) can itself be implemented async if desired. Func<TimeSpan, Task<bool>>
and so there is no dependency on Rx or TPL-Dataflow or what else. New code:
void Main()
{
Task.Run(() => Worker()).Wait();
Console.WriteLine("DONE");
}
async Task Worker()
{
if (SynchronizationContext.Current != null)
throw new InvalidOperationException("Don't want any synchronization!");
var cancellation = new CancellationTokenSource(55000).Token; // gets cancelled after 5.5s
cancellation.Register(() => Console.WriteLine("token is cancelled now"));
var flow = new ActionBlock<TimeSpan>(
async ts =>
{
Console.WriteLine("[START] Elapsed: {0}; cancelled: {1}", ts, cancellation.IsCancellationRequested);
await Task.Delay(2500).ConfigureAwait(false); // processing takes more time than items need to produce
Console.WriteLine("[STOP] Elapsed: {0}; cancelled: {1}", ts, cancellation.IsCancellationRequested);
},
new ExecutionDataflowBlockOptions
{
BoundedCapacity = 2, // Buffer 1 item ahead
EnsureOrdered = true,
CancellationToken = cancellation,
});
Func<TimeSpan, Task<bool>> observer = ts => flow.SendAsync(ts, cancellation);
BaseClass provider = new Implementation();
await provider.CreateValues(observer, cancellation).ConfigureAwait(false);
Console.WriteLine("provider.CreateValues done");
flow.Complete();
await flow.Completion.ConfigureAwait(false);
Console.WriteLine("flow completed");
}
abstract class BaseClass
{
// allow implementers to use async-await
public abstract Task CreateValues(Func<TimeSpan, Task<bool>> observer, CancellationToken cancellation);
}
class Implementation : BaseClass
{
public override async Task CreateValues(Func<TimeSpan, Task<bool>> observer, CancellationToken cancellation)
{
try
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine("{0}/{1} cancelled:{2}", i, j, cancellation.IsCancellationRequested);
Thread.Sleep(333);
}
if (cancellation.IsCancellationRequested)
throw new ApplicationException("token is cancelled");
var value = sw.Elapsed;
var queued = await observer(value); // use of "observer" encorces async-await even if there is nothing else async
Console.WriteLine("[{0}] '{1}' @ {2}", queued ? "enqueued" : "skipped", value, sw.Elapsed);
if (!queued)
; // Dispose item
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.