24

What I want to do is ensure that if the only reference to my observer is the observable, it get's garbage collected and stops receiving messages.

Say I have a control with a list box on it called Messages and this code behind:

//Short lived display of messages (only while the user's viewing incoming messages)
public partial class MessageDisplay : UserControl
{
    public MessageDisplay()
    {
        InitializeComponent();
        MySource.IncomingMessages.Subscribe(m => Messages.Items.Add(m));
    }
}

Which is connecting to this source:

//Long lived location for message store
static class MySource
{
    public readonly static IObservable<string> IncomingMessages = new ReplaySubject<string>;
}

What I don't want is to have the Message Display being kept in memory long after it's no longer visible. Ideally I'd like a little extension so I can write:

MySource.IncomingMessages.ToWeakObservable().Subscribe(m => Messages.Items.Add(m));

I also don't want to rely on the fact that MessageDisplay is a user control as I will later want to go for an MVVM setup with MessageDisplayViewModel which won't be a user control.

ForbesLindesay
  • 10,482
  • 3
  • 47
  • 74
  • 1
    Do you have some place in your code when you know that you don't want the Observable any longer? In that case, you could grab the `IDisposable` returned from the `Subscribe` method to get rid of it when you need to. – lbergnehr Sep 06 '11 at 15:37
  • @seldon I could use that for this specific example, just whenever the message window is closed, but I want a much more general approach so I can use this functionality more widely and to prevent other programmers who use my libraries forgetting to dispose something somewhere. I've seen something related in the MVVMLightToolkit, but not for IObservable and I don't really understand how it works and these things are notoriously difficult to get right. – ForbesLindesay Sep 06 '11 at 15:40
  • You might be referring to the `WeakReference` class, which can be used to make instances get collected by the garbage collector even though they are 'referenced'. However, there are a lot of operators in the reactive extensions that deal with disposing the observable at some point. If you have some event or such for when you know that you're done with the observable, perhaps those should suffice? – lbergnehr Sep 06 '11 at 15:48
  • 2
    I would suggest you are actively trying to adopt an anti pattern. MVVM done properly works just fine with the IDispose pattern. You should very much so implement this properly. – Lee Campbell Feb 13 '12 at 18:05

6 Answers6

16

You can subscribe a proxy observer to the observable that holds a weak reference to the actual observer and disposes the subscription when the actual observer is no longer alive:

static IDisposable WeakSubscribe<T>(
    this IObservable<T> observable, IObserver<T> observer)
{
    return new WeakSubscription<T>(observable, observer);
}

class WeakSubscription<T> : IDisposable, IObserver<T>
{
    private readonly WeakReference reference;
    private readonly IDisposable subscription;
    private bool disposed;

    public WeakSubscription(IObservable<T> observable, IObserver<T> observer)
    {
        this.reference = new WeakReference(observer);
        this.subscription = observable.Subscribe(this);
    }

    void IObserver<T>.OnCompleted()
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnCompleted();
        else this.Dispose();
    }

    void IObserver<T>.OnError(Exception error)
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnError(error);
        else this.Dispose();
    }

    void IObserver<T>.OnNext(T value)
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnNext(value);
        else this.Dispose();
    }

    public void Dispose()
    {
        if (!this.disposed)
        {
            this.disposed = true;
            this.subscription.Dispose();
        }
    }
}
dtb
  • 213,145
  • 36
  • 401
  • 431
  • +1 this exactly the sort of thing I was hoping for. One quick question though is will it still work with the extension methods for Subscribe(M=>DoSomethingWithM(M)), or might they get garbage collected to early? As an extension of that, does it matter whether this is the last thing in your query or can you perform projection/query etc. after this as well? – ForbesLindesay Sep 06 '11 at 15:53
  • 4
    You should perform projections and filters before this. Subscribe(M=>DoSomethingWithM(M)) internally creates an IObserver that wraps the delegate M=>DoSomethingWithM(M). You need to keep the internally created IObserver alive, which is not possible, because it's internal. So, after thinking about it, I wouldn't actually recommend my answer any longer. Look for a different approach. – dtb Sep 06 '11 at 16:00
  • Is there any requirement that the IDisposable returned when subscribing an event allow for asynchronous disposal? It would seem like it should be easy enough to implement such behavior by having it contain the reference to the IObserver, and having it clear that reference on Dispose even if the threading context wouldn't allow it to do anything else; if that were done, the IObservable could avoid memory leaks by periodically purging its subscription list of Disposed subscribers. Examining one subscriber every time a subscription is added or found to have been removed would suffice. – supercat Jan 17 '12 at 16:20
  • Unfortunately, even though imposing such a requirement on IObservable would hardly be onerous, I've not seen anything that actually does impose such a requirement, nor do I know to what extent existing implementations would comply. – supercat Jan 17 '12 at 16:21
3

Ran across this thread a couple years later...just wanted to point forward to the solution identified on Samuel Jack's blog which adds an extension method to IObservable called WeaklySubscribe. It uses an approach of adding a shim between the subject and observer that tracks the target with a WeakReference. That is similar to solutions offered by others for the problem of strong references in event subscriptions, such as in this article or this solution by Paul Stovell. Having for awhile used something based on Paul's approach I like Samuel's solution to weak IObservable Subscribes.

DennisWelu
  • 788
  • 13
  • 26
2

There is another option using the weak-event-patterns

Essentially System.Windows.WeakEventManager has you covered.

Using MVVM when your ViewModel relies on services with events you can weakly subscribe to those services allowing your ViewModel to be collected with the view without the event subscription keeping it alive.

using System;
using System.Windows;

class LongLivingSubject
{ 
    public event EventHandler<EventArgs> Notifications = delegate { }; 
}

class ShortLivingObserver
{
    public ShortLivingObserver(LongLivingSubject subject)
    { 
        WeakEventManager<LongLivingSubject, EventArgs>
            .AddHandler(subject, nameof(subject.Notifications), Subject_Notifications); 
    }

    private void Subject_Notifications(object sender, EventArgs e) 
    { 
    }
}
Johannes
  • 6,490
  • 10
  • 59
  • 108
1

this is my implementation (quit simple one)

public class WeakObservable<T>: IObservable<T>
{
    private IObservable<T> _source;

    public WeakObservable(IObservable<T> source)
    {
        #region Validation

        if (source == null)
            throw new ArgumentNullException("source");

        #endregion Validation

        _source = source;
    }

    public IDisposable Subscribe(IObserver<T> observer)
    {
        IObservable<T> source = _source;
        if(source == null)
            return Disposable.Empty;
        var weakObserver = new WaekObserver<T>(observer);
        IDisposable disp = source.Subscribe(weakObserver);
        return disp;
    }
}
    public class WaekObserver<T>: IObserver<T>
{
    private WeakReference<IObserver<T>> _target;

    public WaekObserver(IObserver<T> target)
    {
        #region Validation

        if (target == null)
            throw new ArgumentNullException("target");

        #endregion Validation

        _target = new WeakReference<IObserver<T>>(target);
    }

    private IObserver<T> Target
    {
        get
        {
            IObserver<T> target;
            if(_target.TryGetTarget(out target))
                return target;
            return null;
        }
    }

    #region IObserver<T> Members

    /// <summary>
    /// Notifies the observer that the provider has finished sending push-based notifications.
    /// </summary>
    public void OnCompleted()
    {
        IObserver<T> target = Target;
        if (target == null)
            return;

        target.OnCompleted();
    }

    /// <summary>
    /// Notifies the observer that the provider has experienced an error condition.
    /// </summary>
    /// <param name="error">An object that provides additional information about the error.</param>
    public void OnError(Exception error)
    {
        IObserver<T> target = Target;
        if (target == null)
            return;

        target.OnError(error);
    }

    /// <summary>
    /// Provides the observer with new data.
    /// </summary>
    /// <param name="value">The current notification information.</param>
    public void OnNext(T value)
    {
        IObserver<T> target = Target;
        if (target == null)
            return;

        target.OnNext(value);
    }

    #endregion IObserver<T> Members
}
    public static class RxExtensions
{
    public static IObservable<T> ToWeakObservable<T>(this IObservable<T> source)
    {
        return new WeakObservable<T>(source);
    }
}
        static void Main(string[] args)
    {
        Console.WriteLine("Start");
        var xs = Observable.Interval(TimeSpan.FromSeconds(1));
        Sbscribe(xs);

        Thread.Sleep(2020);
        Console.WriteLine("Collect");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Console.WriteLine("Done");
        Console.ReadKey();
    }

    private static void Sbscribe<T>(IObservable<T> source)
    {
        source.ToWeakObservable().Subscribe(v => Console.WriteLine(v));
    }
Bnaya
  • 715
  • 5
  • 7
0

The key is to recognize that you are going to have to pass in both the target and a two-parameter action. A one-parameter action will never do it, because either you use a weak reference to your action (and the action gets GC'd), or you use a strong reference to your action, which in turn has a strong reference to the target, so the target can't get GC'd. Keeping that in mind, the following works:

using System;

namespace Closures {
  public static class WeakReferenceExtensions {
    /// <summary> returns null if target is not available. Safe to call, even if the reference is null. </summary>
    public static TTarget TryGetTarget<TTarget>(this WeakReference<TTarget> reference) where TTarget : class {
      TTarget r = null;
      if (reference != null) {
        reference.TryGetTarget(out r);
      }
      return r;
    }
  }
  public static class ObservableExtensions {

    public static IDisposable WeakSubscribe<T, U>(this IObservable<U> source, T target, Action<T, U> action)
      where T : class {
      var weakRef = new WeakReference<T>(target);
      var r = source.Subscribe(u => {
        var t = weakRef.TryGetTarget();
        if (t != null) {
          action(t, u);
        }
      });
      return r;
    }
  }
}

Sample Observable:

using System;
using System.Reactive.Subjects;

namespace Closures {
  public class Observable {
    public IObservable<int> ObservableProperty => _subject;
    private Subject<int> _subject = new Subject<int>();
    private int n;
    public void Fire() {
      _subject.OnNext(n++);
    }
  }
}

Usage:

Class SomeClass {

 IDisposable disposable;

 public void SomeMethod(Observable observeMe) {
   disposable = observeMe.ObservableProperty.WeakSubscribe(this, (wo, n) => wo.Log(n));
 }

  public void Log(int n) {
    System.Diagnostics.Debug.WriteLine("log "+n);
  }
}
William Jockusch
  • 26,513
  • 49
  • 182
  • 323
-1

The code below is inspired by dtb's original post. The only change is that it returns a reference to the observer as part of the IDisposable. This means that the reference to the IObserver will be kept alive as long as you keep a reference to the IDisposable that you get out at the end of the chain (assuming all disposables keep a reference to the disposable before them). This allows the usage of the extension methods such as Subscribe(M=>DoSomethingWithM(M)) because we keep a reference to the implicitly constructed IObserver but we don't keep a strong reference from the source to the IObserver (which would produce a memory leek).

using System.Reactive.Linq;

static class WeakObservation
{
    public static IObservable<T> ToWeakObservable<T>(this IObservable<T> observable)
    {
        return Observable.Create<T>(observer =>
            (IDisposable)new DisposableReference(new WeakObserver<T>(observable, observer), observer)
            );
    }
}

class DisposableReference : IDisposable
{
    public DisposableReference(IDisposable InnerDisposable, object Reference)
    {
        this.InnerDisposable = InnerDisposable;
        this.Reference = Reference;
    }

    private IDisposable InnerDisposable;
    private object Reference;

    public void Dispose()
    {
        InnerDisposable.Dispose();
        Reference = null;
    }
}

class WeakObserver<T> : IObserver<T>, IDisposable
{
    private readonly WeakReference reference;
    private readonly IDisposable subscription;
    private bool disposed;

    public WeakObserver(IObservable<T> observable, IObserver<T> observer)
    {
        this.reference = new WeakReference(observer);
        this.subscription = observable.Subscribe(this);
    }

    public void OnCompleted()
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnCompleted();
        else this.Dispose();
    }

    public void OnError(Exception error)
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnError(error);
        else this.Dispose();
    }

    public void OnNext(T value)
    {
        var observer = (IObserver<T>)this.reference.Target;
        if (observer != null) observer.OnNext(value);
        else this.Dispose();
    }

    public void Dispose()
    {
        if (!this.disposed)
        {
            this.disposed = true;
            this.subscription.Dispose();
        }
    }
}
ForbesLindesay
  • 10,482
  • 3
  • 47
  • 74
  • 2
    I just tested this code and it still leaks. I strong suggest against trying to do this. There are problems all through this line of thought. 1)Static Replay subject - it will never release its cache 2) If you dont implement the Dispose pattern for Rx what else do you not release? - Event handlers, IO connections? 3) User's can not deterministically dispose of your resources 4) the code does not actually work 5) less is more. You have more code that will fool other coder into thinking that this code works, where it doesn't and just creates 100lines of noise code. **Please dont do this** – Lee Campbell Feb 14 '12 at 08:03
  • @Lee In response to 1, 2, 3: The example scenario in which you might use this has been missed, you would NOT use this in a situation where you needed to ensure the source gets disposed. This is about ensuring the listener gets disposed. It is appropriate if your Observable will be needed for the entire life of the application anyway, but your observer should be disposed if the only thing referencing it is that observable. That pretty much answers 4 - i.e. it does work to ensure that the observer is not leaked – ForbesLindesay Feb 14 '12 at 12:07
  • @Lee to respond to 5: It is a lot of code, and it does something which at first glance seems pretty simple, but turns out to be relatively complex. The reason it's justified to write this large chunk of code is that it's highly reusable. This will work any time you find yourself in the situation above. It can be exposed as an API with some appropriate documentation for what a weak observable does, and could then be used without having to understand how it works. – ForbesLindesay Feb 14 '12 at 12:10
  • I am interested to see an example of this working. I understand that the source cannot be disposed/collected. It is static so it is there for good. How do you suggest that the subscription (ie the Observer) gets disposed. I am making the assumption that you are not actually creating and passing around observers and that you are using the Subscribe extension methods that take actions and implicitly create an internal Observer for you that is disposed on completion/error/unsubscription. – Lee Campbell Feb 14 '12 at 19:01
  • 1
    I am not tryig to be obtuse, it is just that when I ran your code and killed the window, sure the list box stopped getting populated but the Observer was still referenced, the subscription still ran and it still leaked. If however you just mean that the internal observable will be released when you unsubscribe/OnComplete/OnError, this is already what happens. It is the default behaviour of Rx when you use the Subscribe extension methods. I hope I am getting close to understanding. – Lee Campbell Feb 14 '12 at 19:02
  • The point is that, provided you don't separately keep a reference to anything, other than the static observable, you pass an object with a reference to the window into the weakObserver, which only maintains a weak reference, meaning the window can be garbage collected. So one reference chain looks like this: GlobalObject -> IObservable -> Select/Where/etc. queries internal observables/observers -> WeakObserver .....> The IObserver created at the end -> Select/Where/etc. queries internal observables/observers -> The window object. Where ......> represents a weak reference. – ForbesLindesay Feb 14 '12 at 19:53
  • Ordinarily "The IObserver created at the end" would be disposed imediately, so to stop that happening, we return a reference to it as part of our IDisposable (That's what disposable reference does). So we have another chain or reference that goes: The window object -> Select/Where/etc. queries internal observables/observers -> The IObserver created at the end Nothing there keeps a reference to the Window object. You'll need to be careful when testing because there is often a reference to the first window an app creates which is hidden and difficult to dispose of. – ForbesLindesay Feb 14 '12 at 19:57
  • @Lee the thing which needs to be disposed is the Window and all user controls within the window. If you do a quick google search for weak event subscriptions in .net You'll find loads of different attempts at doing it, Observables make it much easier to accomplish this task. – ForbesLindesay Feb 14 '12 at 20:04
  • Still would love to see a proof. I have done a lot of work with WeakReferences (great for Bindings and Controls), I just think that this is done for you in the Subscribe Extension method. Sounds like it is working for you, so that is great. – Lee Campbell Feb 15 '12 at 09:02
  • @Lee I haven't actually used this for a while, so providing proof would mean digging out a lot of stuff from ages ago. The subscribe extension method doesn't do any of this for you. It creates a strong reference and requires you to explicitly dispose of it. This makes sense if you had an object which would otherwise have no references to it, but was doing useful things when sent a message by an IObservable. – ForbesLindesay Feb 15 '12 at 10:42
  • This weak subscription thing seems like it'd throw a wrench in the entirety of Rx. Exactly how do the observers at each layer of the subscriptions get disposed? This behavior is completely inadvisable. If your co-developers can't write good code, you're not going to be able to make up for it by writing bad code yourself. – cwharris May 24 '14 at 06:07