我想要做的是确保如果对观察者的唯一引用是可观察的,则会收集垃圾并停止接收消息。
假设我有一个带有列表框的控件,名为Messages,后面是代码:
//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));
}
}
哪个连接到此来源:
//Long lived location for message store
static class MySource
{
public readonly static IObservable<string> IncomingMessages = new ReplaySubject<string>;
}
我不想要的是让消息显示器在不再可见后很长时间内保存在内存中。理想情况下,我想要一点延伸,所以我可以写:
MySource.IncomingMessages.ToWeakObservable().Subscribe(m => Messages.Items.Add(m));
我也不想依赖MessageDisplay是用户控件的事实,因为我稍后想要使用MessageDisplayViewModel进行MVVM设置,而不是用户控件。
答案 0 :(得分:13)
您可以将代理观察者订阅到包含对实际观察者的弱引用的observable,并在实际观察者不再存活时处置订阅:
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();
}
}
}
答案 1 :(得分:2)
几年后穿过这个线程......只是想指出Samuel Jack's blog上标识的解决方案,它为IObservable添加了一个名为WeaklySubscribe的扩展方法。它使用在主体和观察者之间添加垫片的方法,该垫片使用WeakReference跟踪目标。这类似于其他人针对事件订阅中强引用问题提供的解决方案,例如this article或this solution by Paul Stovell。暂时使用基于保罗方法的东西,我喜欢塞缪尔对弱IObservable订阅的解决方案。
答案 2 :(得分:2)
这是我的实现(退出简单)
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));
}
答案 3 :(得分:2)
还有一个使用weak-event-patterns
的选项基本上System.Windows.WeakEventManager
覆盖了您。
在ViewModel依赖具有事件的服务时使用MVVM,您可以微弱地订阅这些服务,从而可以随视图一起收集ViewModel,而无需事件订阅使其保持活动状态。
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)
{
}
}
答案 4 :(得分:0)
以下代码的灵感来自dtb的原帖。唯一的变化是它返回对作为IDisposable一部分的观察者的引用。这意味着只要您保留对在链末端输出的IDisposable的引用(假设所有一次性用品都保留对它们之前的一次性引用),对IObserver的引用将保持活动状态。这允许使用扩展方法,例如Subscribe(M=>DoSomethingWithM(M))
,因为我们保留对隐式构造的IObserver的引用,但是我们没有保留从源到IObserver的强引用(这会产生内存韭菜)。 / p>
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();
}
}
}
答案 5 :(得分:0)
关键是要认识到你必须同时传递目标和双参数动作。单参数操作永远不会这样做,因为您使用对操作的弱引用(并且操作获得GC),或者您对操作使用强引用,而后者又强烈引用了目标,所以目标无法获得GC。记住这一点,以下工作:
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;
}
}
}
可观察样本:
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++);
}
}
}
用法:
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);
}
}