为什么在所有初始订户断开连接后RefCount无法正常工作?

时间:2016-02-29 05:48:41

标签: c# .net system.reactive

请考虑以下事项:

[Fact]
public void foo()
{
    var result = new Subject<bool>();
    var startCount = 0;
    var completionCount = 0;
    var obs = Observable
        .Defer(() =>
            {
                ++startCount;
                return result.FirstAsync();
            })
        .Do(_ => ++completionCount)
        .Publish()
        .RefCount();

    // pretend there are lots of subscribers at once
    var s1 = obs.Subscribe();
    var s2 = obs.Subscribe();
    var s3 = obs.Subscribe();

    // even so, we only expect to be started once
    Assert.Equal(1, startCount);
    Assert.Equal(0, completionCount);

    // and we won't complete until the result ticks through
    result.OnNext(true);
    Assert.Equal(1, startCount);
    Assert.Equal(1, completionCount);

    s1.Dispose();
    s2.Dispose();
    s3.Dispose();

    // now try exactly the same thing again
    s1 = obs.Subscribe();
    s2 = obs.Subscribe();
    s3 = obs.Subscribe();

    // startCount is 4 here instead of the expected 2!
    Assert.Equal(2, startCount);
    Assert.Equal(1, completionCount);

    result.OnNext(true);
    Assert.Equal(2, startCount);
    Assert.Equal(2, completionCount);

    s1.Dispose();
    s2.Dispose();
    s3.Dispose();
}

我对Publish + RefCount的理解是,只要至少有一个订阅者,就会保持与源的连接。一旦最后一个用户断开连接,任何未来的用户都将重新启动与源的连接。

正如您在我的测试中所看到的,第一次通过时,一切都很完美。但第二次,管道内的延迟可观察对每个新用户执行一次。

我可以通过调试器看到,对于第一组订阅者,obs._count(计算订阅者)会增加每次Subscribe的调用。但对于第二组订户,它仍为零。

为什么会发生这种情况,我该怎么做才能纠正我的管道?

2 个答案:

答案 0 :(得分:3)

@ user631090的答案很接近,但不正确,所以我想我会自己回答。

这是因为如果发布的流本身已经完成,Publish将立即完成新订阅者。你可以在图here中看到它:

enter image description here

但如果图表在基础流完成后包含订阅者,那就太好了。

为了增加混淆,仍然会为新订阅者调用Defer。但由于初始流完成,Publish忽略了它的返回值。

我还无法想出一种方法来实现我的预期用例。我想也许使用Multicast而不是Publish,根据需要创建一个新主题。但我还没有实现这一目标。对于我认为常见的用例而言,这似乎相当痛苦。

答案 1 :(得分:1)

这是因为基础可观察结果已经完成。因此,每个新订阅者只是获得OnCompleted回调。

如果ObservableDefer每次创建一个新序列或者一个未完成的序列,您将看到所需的行为。

e.g。

return result.FirstAsync().Concat(Observable.Never<bool>());

您需要删除Assert.Equal(1, completionCount);