为什么在SubscribeOn()之后不能保证Subject <t> .HasObservers为true?

时间:2019-02-16 10:47:31

标签: c# system.reactive

Subject.HasObservers在滴答次数不确定的情况下附加的示例代码中不是立即为真。如果我删除SubscribeOn(),则HasObservers始终为true,因此我知道这与IScheduler初始化有关。

这在我们的生产软件中造成了一个问题,尽管可以保证在允许调用OnNext()的线程之前初始化IDisposable订阅变量,但对OnNext()的前几个调用却无济于事。这是RX中的错误吗?

还有什么其他方法可以使用System.Reactive类来确保使用调度程序设置订阅而不进行轮询?

我尝试了Subject.Synchronize(),但这没什么区别。

static void Main(string[] args)
{

    for (int i = 0; i < 100; i++)
    {
        var source = new Subject<long>();

        IDisposable subscription = source
            .SubscribeOn(ThreadPoolScheduler.Instance)
            .Subscribe(Console.WriteLine);

        // 0 and 668,000 ticks for subscription setup, but rarely 0.
        int iterations = 0;
        while (!source.HasObservers)
        {
            iterations++;
            Thread.SpinWait(1);
        }

        // Next line would rarely output to Console without while loop
        source.OnNext(iterations);
        subscription.Dispose();
        source.Dispose();
    }

}

我希望Subject.HasObservers是真实的而不进行轮询。

2 个答案:

答案 0 :(得分:1)

据我了解,问题在于您的订阅是异步完成的:该调用没有被阻止,因此真正的订阅将稍后在其他线程上完成。

我没有找到确切的方法来了解订阅是否已真正登陆(甚至根本不可能)。如果您的问题是第一个OnNext与订阅之间的竞争,那么可能需要使用Replay() + Connect()将Observable转换为Connectable Observable。这样,您将确保每个订阅者都获得完全相同的序列。

using (var source = new Subject<long>())
{
    var connectableSource = source.Replay();
    connectableSource.Connect();
    using (var subscription = connectableSource
                    .SubscribeOn(ThreadPoolScheduler.Instance)
                    .Subscribe(Console.WriteLine))
    {
        source.OnNext(42); // outputs 42 always
        Console.ReadKey(false);
    }
}

在我的代码中,由于在另一个线程上进行的订阅与取消订阅之间的竞争,我仍然需要Console.ReadKey

答案 1 :(得分:1)

我现在想出的解决方案是希望有人可以改进:

public class SubscribedSubject<T> : ISubject<T>, IDisposable
{
    private readonly Subject<T> _subject = new Subject<T>();

    private readonly ManualResetEventSlim _subscribed = new ManualResetEventSlim();

    public bool HasObservers => _subject.HasObservers;

    public void Dispose() => _subject.Dispose();

    public void OnCompleted() => Wait().OnCompleted();

    public void OnError(Exception error) => Wait().OnError(error);

    public void OnNext(T value) => Wait().OnNext(value);

    public IDisposable Subscribe(IObserver<T> observer)
    {
        IDisposable disposable = _subject.Subscribe(observer);
        _subscribed.Set();
        return disposable;
    }

    private Subject<T> Wait()
    {
        _subscribed.Wait();
        return _subject;
    }
}

示例用法:

using (var source = new SubscribedSubject<long>())
{
    using (source
        .SubscribeOn(ThreadPoolScheduler.Instance)
        .Subscribe(Console.WriteLine))
    {
        source.OnNext(42);
        Console.ReadKey();
    }
}