Почему обработчик событий продолжает вызываться даже после переназначения источника события? - PullRequest
0 голосов
/ 22 октября 2019

При просмотре некоторого кода я столкнулся с простой ошибкой копирования / вставки, которая приводила к противоречивому интуитивному поведению. Намерение состояло в том, чтобы подписаться на события журнала событий системы и приложения и обработать их обоих. Свернутый код, представленный ниже, выглядит для меня так, как будто первый источник событий для журнала приложений заменяется и поэтому не должен генерировать больше событий после сборки мусора. Тем не менее, я наблюдаю, что любые записи в журнал приложения будут по-прежнему вызывать метод 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");
    }
}
...