为什么我需要在完成后处理订阅?

时间:2017-11-17 20:56:00

标签: system.reactive reactive-programming idisposable reactive object-lifetime

Intro To RX书籍将OnSubscribe的返回值描述为IDisposible,并指出在调用OnErrorOnCompleted时应处理订阅。

  

要考虑的一件有趣的事情是,当一个序列完成或   错误,你仍然应该处理你的订阅。

     

来自Intro to RX: Lifetime Management, OnError and OnCompleted

为什么会这样?

作为参考,这是我目前正在研究的课程。我可能会在某些时候将其提交给代码审查。

using System;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;

/// <summary>
/// Provides a timeout mechanism that will not timeout if it is signalled often enough
/// </summary>
internal class TrafficTimeout
{
    private readonly Action onTimeout;
    private object signalLock = new object();
    private IObserver<Unit> signals;

    /// <summary>
    /// Initialises a new instance of the <see cref="TrafficTimeout"/> class.
    /// </summary>
    /// <param name="timeout">The duration to wait after receiving signals before timing out.</param>
    /// <param name="onTimeout">The <see cref="Action"/> to perform when the the timeout duration expires.</param>
    public TrafficTimeout(TimeSpan timeout, Action onTimeout)
    {
        // Subscribe to a throttled observable to trigger the expirey
        var messageQueue = new BehaviorSubject<Unit>(Unit.Default);
        IDisposable subscription = null;
        subscription = messageQueue.Throttle(timeout).Subscribe(
        p =>
        {
            messageQueue.OnCompleted();
            messageQueue.Dispose();
        });

        this.signals = messageQueue.AsObserver();
        this.onTimeout = onTimeout;
    }

    /// <summary>
    /// Signals that traffic has been received.
    /// </summary>
    public void Signal()
    {
        lock (this.signalLock)
        {
            this.signals.OnNext(Unit.Default);
        }
    }
}

2 个答案:

答案 0 :(得分:6)

Subscribe扩展方法返回的一次性返回仅用于允许您手动取消订阅可观察之前可观察的自然结束。

如果observable完成 - 使用OnCompletedOnError - 那么订阅已经为您处理。

试试这段代码:

var xs = Observable.Create<int>(o =>
{
    var d = Observable.Return(1).Subscribe(o);
    return Disposable.Create(() =>
    {
        Console.WriteLine("Disposed!");
        d.Dispose();
    });
});

var subscription = xs.Subscribe(x => Console.WriteLine(x));

如果你运行上述内容,你会看到“Disposed!”在observable完成时写入控制台,而不需要在订阅上调用.Dispose()

需要注意的一件重要事情:垃圾收集器永远不会在可观察订阅上调用.Dispose(),因此如果他们之前没有(或可能没有)自然结束,那么必须处理您的订阅您的订阅超出范围。

拿这个,例如:

var wc = new WebClient();

var ds = Observable
    .FromEventPattern<
        DownloadStringCompletedEventHandler,
        DownloadStringCompletedEventArgs>(
            h => wc.DownloadStringCompleted += h,
            h => wc.DownloadStringCompleted -= h);

var subscription =
    ds.Subscribe(d =>
        Console.WriteLine(d.EventArgs.Result));

ds observable仅在具有订阅时附加到事件处理程序,并且仅在observable完成或处理订阅时才分离。因为它是一个事件处理程序,所以observable永远不会完成,因为它正在等待更多的事件,因此处理是从事件中分离的唯一方法(对于上面的例子)。

当你有一个FromEventPattern observable,你知道它只会返回一个值,那么在订阅之前添加.Take(1)扩展方法是明智的,允许事件处理程序自动分离然后你不要需要手动处理订阅。

像这样:

var ds = Observable
    .FromEventPattern<
        DownloadStringCompletedEventHandler,
        DownloadStringCompletedEventArgs>(
            h => wc.DownloadStringCompleted += h,
            h => wc.DownloadStringCompleted -= h)
    .Take(1);

我希望这会有所帮助。

答案 1 :(得分:2)

首先,这是一个可以遇到的麻烦的例子:

void Main()
{
    Console.WriteLine(GC.GetTotalMemory(true));
    for (int i = 0; i < 1000; i++)
    {
        DumbSubscription();
        Console.WriteLine(GC.GetTotalMemory(true));
    }
    Console.WriteLine(GC.GetTotalMemory(true));
}

public void DumbSubscription()
{
    Observable.Interval(TimeSpan.FromMilliseconds(50))
        .Subscribe(i => {});
}

您将看到您的内存使用率永远上升。 Active Rx订阅不会被垃圾收集,这种可观察性是无限的。因此,如果你增加了循环限制,或者增加了延迟,那么你只会浪费更多的内存:除了处理这些订阅之外,什么都不会帮助你。

但是,假设我们将DumbSubscription的定义更改为:

public void DumbSubscription()
{
    Observable.Interval(TimeSpan.FromMilliseconds(50))
        .Take(1)
        .Subscribe(i => {});
}

.Take(1)加法意味着observable将在一个间隔后完成,因此它不再是无限的。您将看到您的内存使用情况稳定:订阅倾向于在完成或异常时正确处置自己。

但是,与其他任何IDisposable一样,这并不会改变这样一个事实,即调用Dispose(手动或通过using)来确保资源是最佳实践妥善处置。另外,如果你调整你的observable,你可以很容易地遇到开头指出的内存泄漏问题。