简体   繁体   English

为什么会收集对事件处理程序垃圾的这种弱引用?

[英]Why is this weak reference to event handler garbage collected?

Why is garbage-collected event handler in following example? 在以下示例中,为什么要垃圾收集事件处理程序?
I would expect event received after garbage collection, but it is not. 我希望垃圾收集后收到事件,但事实并非如此。
Question is not about WeakEventManager. 问题不关乎WeakEventManager。

class WeakEventTest
{
    public static void Run() {
        EventConsumer ec = new EventConsumer();
        WeakEvent<EventArgs> weakEvent = new WeakEvent<EventArgs>();
        EventHandler<EventArgs> eh = ec.HandleEvent;
        weakEvent += new WeakReference<EventHandler<EventArgs>>(ec.HandleEvent);

        Console.WriteLine("Calling trigger");
        weakEvent.Trigger(null, EventArgs.Empty);

        Console.WriteLine("Calling System.GC.Collect");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.WaitForFullGCComplete();
        GC.Collect();

        // event handler not called here
        Console.WriteLine("Calling trigger");
        weakEvent.Trigger(null, EventArgs.Empty);
    }


}

class EventConsumer
{
    public void HandleEvent(object obj, EventArgs args)
    {
        Console.WriteLine("EventReceived");
    }
}

public class WeakEvent<T>
{
    private List<WeakReference<EventHandler<T>>> referenceList = new List<WeakReference<EventHandler<T>>>();
    private EventHandler<T> handler = null;

    public static WeakEvent<T> operator +(WeakEvent<T> a, EventHandler<T> b)
    {
        lock (a.referenceList)
        {
            a.handler += b;
        }
        return a;
    }

    public static WeakEvent<T> operator +(WeakEvent<T> a, WeakReference<EventHandler<T>> b)
    {
        lock (a.referenceList)
        {
            a.referenceList.Add(b);
        }
        return a;
    }

    public static WeakEvent<T> operator -(WeakEvent<T> a, EventHandler<T> b)
    {
        lock (a.referenceList)
        {
            for (int i = a.referenceList.Count - 1; i >= 0; i--)
            {
                WeakReference<EventHandler<T>> wr = a.referenceList[i];
                EventHandler<T> target;
                if (!wr.TryGetTarget(out target))
                {
                    a.referenceList.RemoveAt(i);
                    continue;
                }
                if (Object.ReferenceEquals(target, b))
                {
                    a.referenceList.RemoveAt(i);
                    break;
                }
            }
            a.handler -= b;
        }
        return a;
    }

    public void Trigger(object obj, T args)
    {
        lock (referenceList)
        {
            for (int i = referenceList.Count - 1; i >= 0; i--)
            {
                WeakReference<EventHandler<T>> wr = referenceList[i];
                EventHandler<T> target;
                if (!wr.TryGetTarget(out target))
                {
                    referenceList.RemoveAt(i);
                    continue;
                }
                target(obj, args);
            }

            if (handler != null)
            {
                handler(obj, args);
            }
        }
    }

    public WeakEvent<T> AddWeakHandler(EventHandler<T> b)
    {
        lock (referenceList)
        {
            referenceList.Add(new WeakReference<EventHandler<T>>(b));
        }
        return this;
    }

Output in console is: 控制台中的输出为:
Calling trigger 呼叫触发
EventReceived 收到事件
Calling System.GC.Collect 调用System.GC.Collect
Calling trigger 呼叫触发
--> here I would expect EventReceived ->在这里我希望EventReceived

In following simple example reference is not garbage collected and works as expected. 在下面的简单示例中,引用不是垃圾回收,而是按预期工作。

class Program
{
    static void Main(string[] args)
    {
        var ec = new EventConsumer();
        var wr = new WeakReference<EventHandler<EventArgs>>(ec.EventReceived);

        EventHandler<EventArgs> target;
        if (wr.TryGetTarget(out target))
        {
            Console.WriteLine("Raising event");
            target(null, EventArgs.Empty);
        }

        Console.WriteLine("Calling System.GC.Collect");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.WaitForFullGCComplete();
        GC.Collect();

        EventHandler<EventArgs> target2;
        if (wr.TryGetTarget(out target2))
        {
            Console.WriteLine("Raising event");
            target2(null, EventArgs.Empty);
        }

        Console.ReadKey();
    }
}

public class EventConsumer
{
    public void EventReceived(object obj, EventArgs args)
    {
        Console.WriteLine("EventReceived");
    }
}

The first one is the expected result: you're referencing ec only using weak references, so it has no reason not to be collected. 第一个是预期的结果:您仅使用弱引用来引用ec ,因此没有理由不对其进行收集。

The second example is more subtle: ec is kept alive because you're keeping a reference on target (which in turn references ec ). 第二个示例更加微妙: ec保持活动状态,因为您在target上保留了一个引用(反过来又引用了ec )。 Just clear that reference and you'll observe the same behavior as the first example: 只需清除该引用,您将观察到与第一个示例相同的行为:

class Program
{
    static void Main(string[] args)
    {
        var ec = new EventConsumer();
        var wr = new WeakReference<EventHandler<EventArgs>>(ec.EventReceived);

        EventHandler<EventArgs> target;
        if (wr.TryGetTarget(out target))
        {
            Console.WriteLine("Raising event");
            target(null, EventArgs.Empty);
        }

        // Clear the reference
        target = null;

        Console.WriteLine("Calling System.GC.Collect");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.WaitForFullGCComplete();
        GC.Collect();

        EventHandler<EventArgs> target2;
        if (wr.TryGetTarget(out target2))
        {
            Console.WriteLine("Raising event");
            target2(null, EventArgs.Empty);
        }

        Console.ReadKey();
    }
}

public class EventConsumer
{
    public void EventReceived(object obj, EventArgs args)
    {
        Console.WriteLine("EventReceived");
    }
}

Note that you need to compile in release mode. 请注意,您需要在发布模式下进行编译。 In debug mode, objects are kept alive until the end of the method (even if they're not referenced anymore) to make debugging easier. 在调试模式下,对象将保持活动状态直到方法结束(即使不再引用它们)也使调试更加容易。

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

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