3

我们的部分 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();
    }
  }
}

要复制:

  1. 使用插件运行 Petrel
  2. 加载带有包含对象的文件夹的项目
  3. 选择文件夹
  4. 激活插件菜单项
  5. 打开弹出窗口,删除文件夹中的对象
  6. 关闭弹出的表单
  7. 删除文件夹中的对象

消息日志应该清楚地显示在处理表单后仍然调用事件处理程序。

4

1 回答 1

3

您已经通过连接事件保留了对底层可枚举的引用。事件也是参考。只需保留对可枚举的引用并从与您订阅的实例相同的实例中取消订阅。

要处理用户删除对象的问题,您需要监听删除事件。

于 2012-06-12T19:02:31.043 回答