C# 为什么即使在事件源被重新分配之后,事件处理程序仍继续被调用?
在查看一些代码时,我遇到了一个简单的复制/粘贴错误,导致了违反直觉的行为。其目的是订阅系统和应用程序事件日志事件并同时处理它们。在我看来,下面的简化代码似乎第一个事件生成器(用于应用程序日志)被替换,因此在垃圾收集之后不应再生成任何事件。然而,我观察到的是,对应用程序日志的任何写入仍将触发ApplicationEventLog\u EntryWrite方法 我假设有东西保留了对原始EventLog实例的引用,但我想了解是什么以及为什么C# 为什么即使在事件源被重新分配之后,事件处理程序仍继续被调用?,c#,events,reference,event-handling,C#,Events,Reference,Event Handling,在查看一些代码时,我遇到了一个简单的复制/粘贴错误,导致了违反直觉的行为。其目的是订阅系统和应用程序事件日志事件并同时处理它们。在我看来,下面的简化代码似乎第一个事件生成器(用于应用程序日志)被替换,因此在垃圾收集之后不应再生成任何事件。然而,我观察到的是,对应用程序日志的任何写入仍将触发ApplicationEventLog\u EntryWrite方法 我假设有东西保留了对原始EventLog实例的引用,但我想了解是什么以及为什么 class Program { public sta
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");
}
}
您仍然注册了事件处理程序,以清除使用
-=
事件延迟事件处理程序注销事件处理程序是常见的泄漏源。调用new()时,不会清除内部EntryWrittenEventHandler
。处理以前的ApplicationEventLog
(这也将调用StopRaisingEvents
清理方法)。最好取消订阅您订阅的事件并处理一次性对象,对吗?@Christopher该链接似乎证实了我的困惑:那么“publisher”将使“target”保持活动状态,但“target”不会使“publisher”保持活动状态。entrywriteneventhandler
委托不属于ApplicationEventLog
对象。当您new()
后者时,前者仍处于活动状态。新的ApplicationEventLog
添加了一个新的处理程序。