我们的部分 UI 使用 IObservableElementEnumerable.EnumerableChanged 来更新用户,例如从文件夹中删除域对象。
当 UI 被释放时,我们取消订阅事件......或者我们认为。事实证明,取消订阅没有任何效果,我们的事件处理程序仍然被调用。这导致了许多奇怪的错误,但也导致了内存泄漏。
唯一一次取消订阅是,如果我们存储 IObservableElementEnumerable 引用而不是再次调用 IObservableElementEnumerableFactory.GetEnumerable(obj)。但是,这反过来很可能会保留对文件夹对象的实时引用,如果文件夹本身被用户删除,这将中断。
这尤其令人费解,因为 GetEnumerable() 文档清楚地指出:“预计使用相同域对象的后续调用将产生相同的 IObservableElementEnumerable 实例。” 这不应该被解释为保证吗?
是否应该有任何理由取消订阅不起作用?
以下代码复制了 Petrel 2011 上的问题(添加到带有菜单扩展的简单插件,或在此处获取完整解决方案(DropBox)):
using System;
using System.Linq;
using System.Windows.Forms;
using Slb.Ocean.Core;
using Slb.Ocean.Petrel;
using Slb.Ocean.Petrel.Basics;
using Slb.Ocean.Petrel.UI;
namespace ObservableElementEnumerable
{
public class OEEForm : Form
{
private Droid _droid;
private bool _disposed;
public OEEForm()
{
IInput input = PetrelProject.Inputs;
IIdentifiable selected = input.GetSelected<object>().FirstOrDefault() as IIdentifiable;
if (selected == null)
{
PetrelLogger.InfoOutputWindow("Select a folder first");
return;
}
_droid = selected.Droid;
GetEnumerable().EnumerableChanged += enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable subscribed");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && !_disposed)
{
GetEnumerable().EnumerableChanged -= enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable unsubscribed (?)");
_droid = null;
_disposed = true;
}
}
IObservableElementEnumerable GetEnumerable()
{
if (_disposed)
throw new ObjectDisposedException("OEEForm");
object obj = DataManager.Resolve(_droid);
IObservableElementEnumerableFactory factory = CoreSystem.GetService<IObservableElementEnumerableFactory>(obj);
IObservableElementEnumerable enumerable = factory.GetEnumerable(obj);
return enumerable;
}
void enumerable_EnumerableChanged(object sender, ElementEnumerableChangeEventArgs e)
{
PetrelLogger.InfoOutputWindow("Enumerable changed");
if (_disposed)
PetrelLogger.InfoOutputWindow("... but I am disposed and unsubscribed!");
}
}
public static class Menu1
{
public static void OEEBegin1_ToolClick(object sender, System.EventArgs e)
{
OEEForm f = new OEEForm();
f.Show();
}
}
}
要复制:
- 使用插件运行 Petrel
- 加载带有包含对象的文件夹的项目
- 选择文件夹
- 激活插件菜单项
- 打开弹出窗口,删除文件夹中的对象
- 关闭弹出的表单
- 删除文件夹中的对象
消息日志应该清楚地显示在处理表单后仍然调用事件处理程序。