[英]Observable.Create: CancellationToken doesn't transition to IsCancellationRequested
Take this little script (designed in LINQPad but should run everywhere): 使用以下小脚本(在LINQPad中设计,但应在任何地方运行):
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. 方法
Implementation.CreateValues
会一直运行整个10秒钟,而不是在5.5秒后停止运行。 The CancellationToken
passed in by Observable.Create
doesn't even transition to a cancelled state (the original token of course does)! Observable.Create
传入的CancellationToken
甚至不会转换为取消状态(原始令牌当然会转换)!
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. 传递给
subscribeAsync
函数的取消令牌是由Observable.Create
调用实例化的,而不是您要实例化的取消令牌。
As per the Observable.Create
overload summary: 根据
Observable.Create
重载摘要:
Creates an observable sequence from a specified cancellable asynchronous Subscribe method.
从指定的可取消异步Subscribe方法创建一个可观察的序列。 The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation.
传递给异步Subscribe方法的CancellationToken绑定到返回的一次性订阅,从而可以尽力而为取消。
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): 这实际上不是问题的答案,而是使用System.Threading.Tasks.Dataflow代替System.Reactive重写示例代码(太多的代码无法发布为注释):
This has several advantages: 这有几个优点:
observer
parameter is now a Task
every implementation has something to await
for. observer
参数现在是一个Task
每个实现都需要await
。 Do()
(now in ActionBlock
) can itself be implemented async if desired. Do()
(现在在ActionBlock
)中的处理代码本身可以异步实现。 Func<TimeSpan, Task<bool>>
and so there is no dependency on Rx or TPL-Dataflow or what else. Func<TimeSpan, Task<bool>>
,因此不依赖Rx或TPL-Dataflow或其他任何东西。 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;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.