首页 > 解决方案 > 为什么事件处理程序在重新分配事件源后仍继续被调用?

问题描述

在查看一些代码时,我遇到了一个简单的复制/粘贴错误,导致反直觉行为。目的是订阅系统和应用程序事件日志事件并同时处理它们。在我看来,下面的精简代码好像应用程序日志的第一个事件生产者被替换,因此在垃圾收集后不应再产生任何事件。但是,我观察到对应用程序日志的任何写入仍将触发 ApplicationEventLog_EntryWritten 方法。

然后我假设某些东西保留了对原始 EventLog 实例的引用,但我想了解什么以及为什么?

class Program
{
    public static EventLog ApplicationEventLog;

    static void Main(string[] args)
    {
        ApplicationEventLog = new EventLog("Application");
        ApplicationEventLog.EnableRaisingEvents = true;
        ApplicationEventLog.EntryWritten += ApplicationEventLog_EntryWritten;

        //new instance should have been assigned to new variable
        //public static EventLog sytemEventLog;
        ApplicationEventLog = new EventLog("System");
        ApplicationEventLog.EnableRaisingEvents = true;
        ApplicationEventLog.EntryWritten += SystemEventLog_EntryWritten;

        //for illustrative purposes
        //Original EventLog instance is no longer assigned to anything so I would expect it to get GC'd 
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);

        Console.WriteLine("Press ESC to stop");
        do
        {
            while (!Console.KeyAvailable)
            {
                Task.Delay(250);
            }
        } while (Console.ReadKey(true).Key != ConsoleKey.Escape);
    }

    private static void ApplicationEventLog_EntryWritten(object sender, EntryWrittenEventArgs e)
    {
        Debug.WriteLine("Application log written to");
    }
    private static void SystemEventLog_EntryWritten(object sender, EntryWrittenEventArgs e)
    {
        Debug.WriteLine("System log written to");
    }
}

更新示例:

此版本将所有事件日志代码从图片中删除,以获得更清晰的示例:

class Program
{
    static void Main(string[] args)
    {
        var producer = new Producer();
        producer.SomeEvent += EventConsumer1;
        //Obviously unsubscribing before reassignment works but why does the subscription prevent GC of this first instance?
        //producer.SomeEvent -= EventConsumer1;

        producer = new Producer();
        producer.SomeEvent += EventConsumer2;


        Console.WriteLine("Press ESC to stop");
        do
        {
            while (!Console.KeyAvailable)
            {
                //for illustrative purposes
                //Original producer instance is no longer assigned to anything so I would expect it to get GC'd 
                GC.WaitForPendingFinalizers();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);

                Task.Delay(250);
            }
        } while (Console.ReadKey(true).Key != ConsoleKey.Escape);
    }


    private static void EventConsumer1(object sender, EventArgs e)
    {
        Debug.WriteLine("I would have expected this to get hit once at most");
    }
    private static void EventConsumer2(object sender, EventArgs e)
    {
        Debug.WriteLine("Should hit this periodically");
    }
}

public class Producer
{
    public event EventHandler SomeEvent;

    public Producer()
    {
        SendEventsAsync();
    }

    private async void SendEventsAsync()
    {
        while (true)
        {
            SomeEvent?.Invoke(this, new EventArgs());
            await Task.Delay(5000);
        }
    }

    ~Producer()
    {
        Debug.WriteLine("Instance being garbage collected");
    }
}

标签: c#eventsreferenceevent-handling

解决方案


推荐阅读