【问题标题】:Detect when a COM Object goes out of Scope检测 COM 对象何时超出范围
【发布时间】:2011-08-10 01:19:43
【问题描述】:

我有一个向 COM 公开的 .NET 组件。

这个组件有一个在内部运行的线程,它连接到一个命名管道,并根据需要处理与管道的断开连接和重新连接。

线程在 COM 程序中实例化的组件实例的生命周期内运行(在此实例中为 vb6)。

如果 COM 应用程序正在关闭,但没有调用优雅地关闭线程(和管道)的“关闭”方法,有没有办法检测它们正在关闭应用程序,这样我就不需要依靠他们正确遵守规则?

线程设置了 ISBackground = true。

这是我的线程的基本部分

private System.IO.Pipes.NamedPipeClientStream mPipe;
private System.Threading.AutoResetEvent mConnectedHold;

private void ConnectPipe()
{
    while (mRunning)
    {
        try
        {
            mConnecting = true;
            mConnected = false;
            if (mPipe != null)
            {
                mPipe.Close();
                mPipe.Dispose();
            }
            mPipe = new System.IO.Pipes.NamedPipeClientStream(@".", @"MyPipe", System.IO.Pipes.PipeDirection.InOut, System.IO.Pipes.PipeOptions.Asynchronous);
            mPipe.Connect(1000);

            mConnecting = false;
            mConnected = true;

            mPipe.BeginRead(mBuffer, 0, mBuffer.Length, new AsyncCallback(ReadPipe), null);

            mConnectedHold.WaitOne();
        }
        catch (TimeoutException ex)
        {
        System.Diagnostics.Debug.Print("Not connected yet");
        }
        catch (Exception ex)
        {
            mRunning = false;
        }
    }
}

收盘是相当基本的。

public void Close()
{
    mRunning = false;
    if (mPipe != null )
    {
        mPipe.Close()
        mPipe.Dispose()
    }
    mConnected = false;
}

【问题讨论】:

  • COM 对象并没有像 C# 中的对象那样真正具有“范围”——它们使用引用计数,所以我猜当引用计数达到 0 时会检测到等效项(并且对象被清理)。

标签: .net multithreading com


【解决方案1】:

线程设置了 ISBackground = true。

COM 对您的线程一无所知,尤其是因为它根本不支持线程。将 Thread.IsBackground 属性设置为 true 会告诉 CLR 在程序的主线程退出时可以中止线程。因此,看到它在没有诊断的情况下突然终止也就不足为奇了。

您的 mRunning 变量可能存在问题,需要声明它volatile。您在线程中使用它的方式使其很可能永远不会看到发布版本中变量的更改。抖动优化器易于优化代码并将变量值加载到 CPU 寄存器中。 volatile 关键字可以防止这种情况发生。

这仍然是一个半生不熟的解决方案,你真的应该使用 ManualResetEvent 来通知线程。在你的 Close() 方法中调用它的 Set() 方法,在线程中调用 WaitOne(0) 来测试它。然后,您还必须等待线程退出,以便处理管道不会炸毁线程代码。注意死锁。

现在您不再需要 Thread.IsBackground 并且可以更好地调试此代码。接下来您需要做的是实现 IDisposable 并为您的类编写一个终结器,以确保您的代码正确关闭,即使客户端代码没有很好地调用 Close() 方法。除了忘记这样做之外,如果客户端代码被炸毁,这也是必要的。让您的 Close() 方法调用 Dispose()。根据应用程序是否以正常方式关闭,您可能希望对管道执行两种不同的操作。实现 Disposing 模式,您可以通过 disposing 参数来区分“不错”的关闭(调用关闭)和讨厌的关闭(调用终结器)。

现在,无论哪种方式,您的代码都具有弹性。找出客户端代码没有调用 Close() 的原因应该很简单。

【讨论】:

  • 是的,我解释了你如何防止这种情况发生。
  • 这只是一个sn-p..我知道为什么客户端没有调用close..因为他们实际上并没有编写代码行....在他们的VB6代码中关闭,他们只是将其设置为空。我试图防止这种情况发生。我已经在代码中有 IDispose 了。 Close 是处理 Dispose 的 Exposed to COM 方法。我将添加终结者。
  • 谢谢。复活节休息后我回去工作时会重新编写代码。
【解决方案2】:

也许使用AppDomain events 之一。 “DomainUnload”和“ProcessExit”看起来不错。

实际上,听起来您打算让托管代码保持运行,即使 COM 应用程序出现故障也是如此。也许然后更改您的 WaitOne 以采用 TimeSpan 参数以定期唤醒,并测试客户端 COM 应用程序是否仍然存在。

【讨论】:

  • 查看 appdomain 事件还没有看到任何东西,所以我会继续寻找。我确实希望线程退出,因为它连接的 PipeServer 托管在其他地方(并且可以独立关闭),因此是重新连接代码。我只模拟了几次保持活动的线程,所以我认为这是来自组件的 VB6 用法(没有直接调用 Close)
【解决方案3】:

我前段时间调查了一下,得出的结论是无法检测 .Net COM 对象何时被释放。

我发现的最接近的事情是有人试图通过更改 CCW 的 vtable 来替换 IUnknown.Release 方法(Marshal 类中有一些方法允许您执行此类操作)。有一些严重的问题我不记得了。

您只需要构建您的类,以便它可以使用内部引用计数和终结器来处理这种情况。

对我来说,COM 互操作不允许您添加某种 OnFinalRelease 事件似乎很奇怪。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    • 1970-01-01
    • 2014-04-30
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多