简体   繁体   English

WeakEventManager保存对订户的引用

[英]WeakEventManager holds reference to subscriber

I have been using WeakEventManager to avoid memory leaks, and i started to overuse them. 我一直在使用WeakEventManager来避免内存泄漏,我开始过度使用它们。 I created extension methods for example for the INotifyPropertyChanged like: 我为INotifyPropertyChanged创建了扩展方法,例如:


public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action handler)
{
    PropertyChangedEventManager.AddHandler(item, (s, e) => handler(e.PropertyName), string.Empty);
}

Now i quickly realized, that this doesn't work. 现在我很快意识到,这不起作用。 In fact, you can't really use anonymous methods for weak event handling. 实际上,您无法真正使用匿名方法进行弱事件处理。 (If i understand correctly, then the compiler creates a 'closure class' for it (to hold referenced values), which has the handler, but since your closure class is not referenced anywhere, the GC will clear it away, and the event handler will not be called) (如果我理解正确,那么编译器为它创建一个'闭包类'(用于保存引用的值),它具有处理程序,但由于你的闭包类没有在任何地方被引用,因此GC将清除它,并且事件处理程序不会被叫)

Question #1: Is that correct? 问题1:这是正确的吗? I mean is it correct, then when using an anonymous method (or lambda) for a weak event handler, the handler is called only if the GC did not run in the meanwhile (eg it is undeterministic)? 我的意思是它是正确的,然后当使用匿名方法(或lambda)作为弱事件处理程序时,仅当GC没有同时运行时才调用处理程序(例如,它是不确定的)?

Well, i thaught so, so i did some unit tests to make sure i get it right. 嗯,我非常感谢,所以我做了一些单元测试,以确保我做对了。 It seemed allright until i hit the following unit test: 在我进行以下单元测试之前,它似乎还可以:


        class DidRun
        {
            public bool Value { get; set; }
        }
        class TestEventPublisher
        {
            public event EventHandler<EventArgs> MyEvent;
            public void RaiseMyEvent()
            {
                if (MyEvent != null)
                    MyEvent(this, EventArgs.Empty);

            }
        }
        class TestClosure
        {
            public DidRun didRun { get; set; }
            public EventHandler<EventArgs> Handler { get; private set; }
            public TestClosure()
            {
                this.Handler = new EventHandler<EventArgs>((s, e) => didRun.Value = true);
            }
        }
        [TestMethod]
        public void TestWeakReference()
        {
            var raiser = new TestEventPublisher();
            var didrun = new DidRun();
            var closure = new TestClosure { didRun = didrun };
            WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", closure.Handler);
            closure = null;

            GC.Collect();
            GC.Collect();
            raiser.RaiseMyEvent();
            Assert.AreEqual(false, didrun.Value);
        }

Question #2: Can anyone explain my why does this test fail? 问题2:任何人都可以解释为什么这个测试失败了?

Expectation: Here i do not have any closures (i took them out, to make sure what is happening), i simply have an object (closure), that subscribes to an event with the WeakEventManager, and then i drop the reference to it (closure = null;). 期望:这里我没有任何闭包(我把它们拿出来,以确保发生了什么),我只是有一个对象(闭包),用WeakEventManager订阅一个事件,然后我删除它的引用( closure = null;)。

I was expecting the 2 GC.Collect() calls, to clean up my old closure class, so the WeakEventManager would drop the subscriber, and not run the handler, but the test fails. 我期待2个GC.Collect()调用,以清理我的旧闭包类,因此WeakEventManager将删除订阅者,而不是运行处理程序,但测试失败。 Any ideas? 有任何想法吗?

EDIT: Sorry, the generic arguments were not visible, now they are 编辑:对不起,通用参数不可见,现在它们是

You are correct that the GC will collect the closure that was created around your lambda if there is no reference to it. 你是正确的,如果没有对它的引用,GC将收集在你的lambda周围创建的闭包。

In your unit test you null out the local instance of TestClosure but you've passed a hard reference of the handler into WeakEventManager, not an instance of TestClosure . 在单元测试中,您将TestClosure的本地实例TestClosure但是您已将处理程序的硬引用传递给WeakEventManager,而不是TestClosure的实例。 So the handler lives on... 所以处理程序依然存在......

I believe these examples demonstrate your trouble with the closure: 我相信这些例子证明了你关闭的问题:

class DidRun
{
    public bool Value { get; set; }
}

class TestEventPublisher
{
    public event EventHandler<EventArgs> MyEvent;
    public void RaiseMyEvent()
    {
        if (MyEvent != null)
            MyEvent(this, EventArgs.Empty);
    }
}

class TestClosure
{
    static public EventHandler<EventArgs> Register(TestEventPublisher raiser, DidRun didrun)
    {
        EventHandler<EventArgs> handler = (s, e) => didrun.Value = true;
        WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", handler);
        return handler;
    }
}

[TestMethod]
public void Test1()
{
    var raiser = new TestEventPublisher();
    var didrun = new DidRun();

    TestClosure.Register(raiser, didrun);

    // The reference to the closure 'handler' is not being held,
    //  it may or may not be GC'd (indeterminate result)

    raiser.RaiseMyEvent();
    Assert.IsTrue(didrun.Value);
}

[TestMethod]
public void Test2()
{
    var raiser = new TestEventPublisher();
    var didrun = new DidRun();

    // The reference to the closure 'handler' is not being held, it's GC'd
    TestClosure.Register(raiser, didrun);

    GC.Collect();
    GC.Collect();

    raiser.RaiseMyEvent();
    Assert.IsFalse(didrun.Value);
}

[TestMethod]
public void Test3()
{
    var raiser = new TestEventPublisher();
    var didrun = new DidRun();

    // Keep local copy of handler to prevent it from being GC'd
    var handler = TestClosure.Register(raiser, didrun);

    GC.Collect();
    GC.Collect();

    raiser.RaiseMyEvent();
    Assert.IsTrue(didrun.Value);
}

As for your original issue, you can try to save the handler (closure) to prevent it from being GC'd. 至于你的原始问题,你可以尝试保存处理程序(闭​​包)以防止它被GC。 A ConditionalWeakTable should work for this: ConditionalWeakTable应该适用于此:

// ConditionalWeakTable will hold the 'value' as long as the 'key' is not marked for GC
static private ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>> _eventMapping =
  new ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>>();

public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action<string> handlerAction)
{
    EventHandler<PropertyChangedEventArgs> handler;

    // Remove any existing handler for this item in case it's registered more than once
    if (_eventMapping.TryGetValue(item, out handler))
    {   
        _eventMapping.Remove(item);
        PropertyChangedEventManager.RemoveHandler(item, handler, string.Empty);
    }   

    handler = (s, e) => handlerAction(e.PropertyName);

    // Save handler (closure) to prevent GC
    _eventMapping.Add(item, handler);

    PropertyChangedEventManager.AddHandler(item, handler, string.Empty);
}

class DidRun
{
    static public string Value { get; private set; }
    public void SetValue(string value) { Value = value; }
}

[TestMethod]
public void Test4()
{
    var property = new ObservableObject<string>();

    var didrun = new DidRun();
    property.AddWeakPropertyChanged(
        (x) => 
        {
            didrun.SetValue("Property Name = " + x);
        });

    GC.Collect();
    GC.Collect();

    property.Value = "Hello World";

    Assert.IsTrue(DidRun.Value != null);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM