从RefCount()观察到不会停止发布

时间:2019-04-19 23:17:10

标签: c# .net rx.net

我正在建立一条消息处理管道,并注意到当最后一个观察者释放订阅时,可观察对象仍在泵送数据。

我查看了Rx文档,并基于它的假设是,根据文档,一旦最后一个观察者退订,“ RefCount()”将断开可观察对象的连接:

然后

RefCount会跟踪有多少其他观察者订阅了该观察者,并且不会与基础可连接Observable保持断开连接,直到最后一个观察者这样做为止。。

为了说明这个问题,我在下面创建了一个非常简单的示例:

class Program
{
    static void Main(string[] args)
    {
        _ = SimulateObservableIssue();

        Console.ReadKey();
    }

    public static async Task SimulateObservableIssue()
    {
        IObservable<int> source = Observable.Create<int>(async (observer) =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"Source publishing {i}");
                observer.OnNext(i);
                await Task.Delay(1000);
            }

            observer.OnCompleted();

            return Disposable.Create(() => Console.WriteLine("Observable is disposed"));
        });

        var multiSource = source.Publish().RefCount();

        var subscription = multiSource.Subscribe(x => Console.WriteLine("Observer received: " + x));

        await Task.Delay(3000);

        subscription.Dispose();

        Console.WriteLine("Subscription disposed");

    }
}

and the console output

为什么在“ subscription.Dispose()”之后,可观察对象仍在尝试生成数据?

谢谢!

2 个答案:

答案 0 :(得分:0)

您的source可观察者不遵守您提到的可观察者合约。如果您将source替换为:

    var source = Observable.Interval(TimeSpan.FromSeconds(1))
        .Do(i => Console.WriteLine($"Source publishing {i}"), () => Console.WriteLine("Observable is disposed"))
        .Take(10);

...您将看到它按预期工作。

至于为什么,请考虑一个具有两个阶段的可观察对象:订阅和观察。无论取消订阅如何,始终都会在订阅期间发生代码。 Observable.Create代码就是所有订阅代码。

我写的可观察的都是所有观察代码(就像大多数可观察的代码一样)。因此,它可以适当地响应订阅取消。

答案 1 :(得分:0)

您正在观察的行为与RefCount运算符无关。如果您直接订阅source而不是multiSource,则行为将是相同的。

问题与如何将Observable.Create与异步lambda一起使用以创建自定义可观察对象有关。 Rx库无法终止由异步lambda创建的Task。因此,尽管观察者已取消订阅,但任务仍在继续。通常,只能协同终止任务,而终止不再需要的任务的standard mechanism是通过发信号通知任务所观察到的CancellationToken来完成的。 Rx库通过Observable.Create重载并使用以下签名来支持此模式:

public static IObservable<TResult> Create<TResult>(
    Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync);

所提供的CancellationToken由库提供和管理,您的责任是通过终止循环来兑现取消信号。一种方法是在循环内的各个点调用CancellationToken.ThrowIfCancellationRequested()方法。在特定示例中,将CancellationToken作为参数传递给Task.Delay方法就足够了:

var source = Observable.Create<int>(async (observer, cancellationToken) =>
{
    cancellationToken.Register(() => Console.WriteLine("Token is canceled"));
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine($"Source publishing {i}");
        observer.OnNext(i);
        await Task.Delay(1000, cancellationToken);
    }
    observer.OnCompleted();
    Console.WriteLine($"Source is completed");
});

在取消的情况下,await Task.Delay引发的异常将不会被任何人观察到,因为在这一点上,可观察者将没有观察者。除非您要记录它,否则没有理由尝试/捕获它。

您可能已经注意到lambda不会返回Disposable。发生这种情况的原因是,Disposable的角色现在由提供的CancellationToken扮演。库在后台使用了CancellationDisposable,该库在处置时将取消内部CancellationTokenSource,该内部Token被传递给您的方法。