gpt4 book ai didi

c# - 使用 Wea​​kReference 解决 .NET 未注册事件处理程序导致内存泄漏的问题

转载 作者:可可西里 更新时间:2023-11-01 08:48:29 26 4
gpt4 key购买 nike

问题:已注册的事件处理程序创建了从事件到事件处理程序实例的引用。如果该实例无法注销事件处理程序(大概是通过 Dispose),那么垃圾收集器将不会释放实例内存。

例子:

    class Foo
{
public event Action AnEvent;
public void DoEvent()
{
if (AnEvent != null)
AnEvent();
}
}
class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}

void l_AnEvent()
{

}
}

如果我实例化一个 Foo,并将其传递给一个新的 Bar 构造函数,然后释放 Bar 对象,由于 AnEvent 注册,它不会被垃圾收集器释放。

我认为这是内存泄漏,看起来就像我以前的 C++ 时代一样。当然,我可以将 Bar 设为 IDisposable,在 Dispose() 方法中注销事件,并确保在其实例上调用 Dispose(),但我为什么必须这样做?

我首先质疑为什么事件是用强引用实现的?为什么不使用弱引用?事件用于抽象地通知一个对象另一个对象的变化。在我看来,如果事件处理程序的实例不再使用(即,没有对该对象的非事件引用),那么它注册的任何事件都应该自动注销。我错过了什么?

我看过 WeakEventManager。哇,好痛。它不仅非常难以使用,而且其文档也不充分(请参阅 http://msdn.microsoft.com/en-us/library/system.windows.weakeventmanager.aspx——注意“继承人须知”部分有 6 个模糊描述的项目符号)。

我在不同的地方看到过其他讨论,但我觉得没有什么可以用的。我提出了一个基于 WeakReference 的更简单的解决方案,如此处所述。我的问题是:这是否不符合复杂性大大降低的要求?

为了使用该方案,将上述代码修改如下:

    class Foo
{
public WeakReferenceEvent AnEvent = new WeakReferenceEvent();

internal void DoEvent()
{
AnEvent.Invoke();
}
}

class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}

void l_AnEvent()
{

}
}

注意两件事:1. Foo类的修改有两种方式:事件替换为WeakReferenceEvent的实例,如下图;并且事件的调用发生了变化。2. Bar 类未更改。

无需继承 WeakEventManager、实现 IWeakEventListener 等

OK,下面是WeakReferenceEvent的实现。这显示在这里。请注意,它使用我从此处借用的通用 Wea​​kReference : http://damieng.com/blog/2006/08/01/implementingweakreferencet

class WeakReferenceEvent
{
public static WeakReferenceEvent operator +(WeakReferenceEvent wre, Action handler)
{
wre._delegates.Add(new WeakReference<Action>(handler));
return wre;
}

List<WeakReference<Action>> _delegates = new List<WeakReference<Action>>();

internal void Invoke()
{
List<WeakReference<Action>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target();
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}

它的功能很简单。我重写运算符 + 以获得 += 语法糖匹配事件。这将创建对 Action 委托(delegate)的 WeakReferences。这允许垃圾收集器在没有其他人持有时释放事件目标对象(本例中的 Bar)。

在 Invoke() 方法中,只需运行弱引用并调用它们的目标操作。如果发现任何死的(即垃圾收集)引用,请将它们从列表中删除。

当然,这只适用于 Action 类型的委托(delegate)。我尝试使它成为通用的,但在 C# 中遇到了缺少的 where T : delegate!

作为替代方案,只需将 WeakReferenceEvent 类修改为 WeakReferenceEvent ,并将 Action 替换为 Action 。修复编译器错误,你就有了一个可以像这样使用的类:

    class Foo
{
public WeakReferenceEvent<int> AnEvent = new WeakReferenceEvent<int>();

internal void DoEvent()
{
AnEvent.Invoke(5);
}
}

此处显示了包含 和运算符 -(用于删除事件)的完整代码:

class WeakReferenceEvent<T>
{
public static WeakReferenceEvent<T> operator +(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Add(handler);
return wre;
}
private void Add(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
return;
_delegates.Add(new WeakReference<Action<T>>(handler));
}

public static WeakReferenceEvent<T> operator -(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Remove(handler);
return wre;
}
private void Remove(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
{
_delegates.Remove(del);
return;
}
}

List<WeakReference<Action<T>>> _delegates = new List<WeakReference<Action<T>>>();

internal void Invoke(T arg)
{
List<WeakReference<Action<T>>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target(arg);
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action<T>>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}

希望这能帮助其他人在遇到垃圾收集世界中导致内存泄漏的神秘事件时!

最佳答案

我找到了为什么这不起作用的问题的答案。是的,确实,我遗漏了一个小细节:调用 += 来注册事件 (l.AnEvent += l_AnEvent;) 创建了一个隐式 Action 对象。该对象通常仅由事件本身(以及调用函数的堆栈)持有。因此,当调用返回并且垃圾收集器运行时,隐式创建的 Action 对象被释放(现在只有一个弱引用指向它),事件被注销。

一个(痛苦的)解决方案是持有对 Action 对象的引用,如下所示:

    class Bar
{
public Bar(Foo l)
{
_holdAnEvent = l_AnEvent;
l.AnEvent += _holdAnEvent;
}
Action<int> _holdAnEvent;
...
}

这可行,但消除了解决方案的简单性。

关于c# - 使用 Wea​​kReference 解决 .NET 未注册事件处理程序导致内存泄漏的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2814900/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com