简体   繁体   English

Observable.Create:CancellationToken不会过渡到IsCancellationRequested

[英]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: 这有几个优点:

  1. since the observer parameter is now a Task every implementation has something to await for. 由于observer参数现在是一个Task每个实现都需要await
  2. the processing code previously in Do() (now in ActionBlock ) can itself be implemented async if desired. 如果需要,先前在Do() (现在在ActionBlock )中的处理代码本身可以异步实现。
  3. integrated buffering if desired. 如果需要,集成缓冲。
  4. it's decoupled = technology agnostic: My interface is 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM