处置另一个线程拥有的 ActiveX 资源

发布于 2024-09-05 16:46:51 字数 1071 浏览 7 评论 0原文

我在线程和资源处理方面遇到了问题。

我有一个 C# Windows 窗体应用程序,它在线程中运行昂贵的操作。该线程实例化一个 ActiveX 控件 (AxControl)。由于该控件使用大量内存,因此必须对其进行处理。所以我实现了一个 Dispose() 方法,甚至一个析构函数。

线程结束后,析构函数被调用。遗憾的是,这是由 UI 线程调用的。因此调用 activexControl.Dispose();失败并显示消息“COM 对象已与其底层 RCW 分离”,因为该对象属于另一个线程。

如何正确地做到这一点还是这只是我使用的一个糟糕的设计?

(我将代码精简到最少,包括消除任何安全问题。)

class Program
{
    [STAThread]
    static void Main()
    {
        // do stuff here, e.g. open a form

        new Thread(new ThreadStart(RunStuff);

        // do more stuff
    }

    private void RunStuff()
    {
        DoStuff stuff = new DoStuff();
        stuff.PerformStuff();
    }
}

class DoStuff : IDisposable
{
    private AxControl activexControl;

    DoStuff()
    {
        activexControl = new AxControl();
        activexControl.CreateControl(); // force instance
    }

    ~DoStuff()
    {
        Dispose();
    }

    public void Dispose()
    {
        activexControl.Dispose();
    }

    public void PerformStuff()
    {
        // invent perpetuum mobile here, takes time
    }
}

I've got a problem problem with threading and disposing resources.

I've got a C# Windows Forms application which runs expensive operation in a thread. This thread instantiates an ActiveX control (AxControl). This control must be disposed as it uses a high amount of memory. So I implemented a Dispose() method and even a destructor.

After the thread ends the destructor is called. This is sadly called by the UI thread. So invoking activexControl.Dispose(); fails with the message "COM object that has been separated from its underlying RCW", as the object belongs to another thread.

How to do this correctly or is it just a bad design I use?

(I stripped the code down to the minimum including removing any safety concerns.)

class Program
{
    [STAThread]
    static void Main()
    {
        // do stuff here, e.g. open a form

        new Thread(new ThreadStart(RunStuff);

        // do more stuff
    }

    private void RunStuff()
    {
        DoStuff stuff = new DoStuff();
        stuff.PerformStuff();
    }
}

class DoStuff : IDisposable
{
    private AxControl activexControl;

    DoStuff()
    {
        activexControl = new AxControl();
        activexControl.CreateControl(); // force instance
    }

    ~DoStuff()
    {
        Dispose();
    }

    public void Dispose()
    {
        activexControl.Dispose();
    }

    public void PerformStuff()
    {
        // invent perpetuum mobile here, takes time
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

宛菡 2024-09-12 16:46:51

我不清楚实现 Dispose 方法的 ActiveX 控件是什么意思。 IDisposable 模式适用于托管代码。要释放 COM 对象,您通常会使用 Marshal.ReleaseComObject - 也许您是在 AxControl 类中执行此操作,但您没有显示该类的实现。

上面的代码有一些问题。

您应该处置 IDisposable DoStuff 实例:

private void RunStuff() 
{ 
    using (DoStuff stuff = new DoStuff())
    {
        stuff.PerformStuff(); 
    }
} 

您不应该在终结器中访问托管资源 - 在您的情况下,终结器调用 Dispose,然后引用托管 axControl 实例。在终结器运行时,该实例可能已经被收集。

由于您没有直接在 DoStuff 类中使用非托管资源,因此您可能不需要终结器,但如果您确实有终结器,请遵循 MSDN 上的标准 IDisposable 模式,并且不要尝试处置任何托管对象。

更新

评论:

AxControl 是由 Visual Studio 生成的 .NET Interop Wrapper DLL。

在这种情况下,Dispose() 方法是什么?我不明白为什么要在具有确定性终结的 ActiveX 控件中实现这样的方法 - 通常您会在释放最后一个 COM 引用时进行清理。

您的 DoStuff.Dispose 方法可能想要释放 COM 对象,例如

public void Dispose()  
{  
    activexControl.Dispose();  
    Marshal.ReleaseComObject(activexControl);
}

I'm not clear what you mean by an ActiveX Control that implements a Dispose method. The IDisposable pattern is intended for managed code. To release a COM object, you would normally use Marshal.ReleaseComObject - perhaps you're doing that inside your AxControl class, whose implementation you don't show.

A couple of things wrong with the above code.

You should be disposing the IDisposable DoStuff instance:

private void RunStuff() 
{ 
    using (DoStuff stuff = new DoStuff())
    {
        stuff.PerformStuff(); 
    }
} 

You should not be accessing managed resources in a finalizer - in your case the finalizer calls Dispose which then references the managed axControl instance. This instance may have already been collected by the time your finalizer runs.

Since you are not using unmanaged resources directly in the DoStuff class, you probably don't need a finalizer, but if you do have one, follow the standard IDisposable pattern on MSDN, and don't attempt to dispose any managed objects.

UPDATE

Comment:

The AxControl is the .NET Interop Wrapper DLL which is generated by Visual Studio.

In this case, what is the Dispose() method? I don't see why you would implement such a method in an ActiveX control, which has deterministic finalization - normally you'd do your cleanup when the last COM reference is released.

Your DoStuff.Dispose method might want to release the COM object, e.g.

public void Dispose()  
{  
    activexControl.Dispose();  
    Marshal.ReleaseComObject(activexControl);
}
趁微风不噪 2024-09-12 16:46:51

为什么不让工作线程显式地处理它?

您可以将 更改

DoStuff stuff = new DoStuff();
stuff.PerformStuff();

为 a

using(DoStuff stuff = new DoStuff())
{
     stuff.PerformStuff();
}

所以您甚至不必担心它。

Why don't you have the worker thread dispose of it explicitly?

You could possible change the

DoStuff stuff = new DoStuff();
stuff.PerformStuff();

To a

using(DoStuff stuff = new DoStuff())
{
     stuff.PerformStuff();
}

So you don't even have to worry about it.

慕巷 2024-09-12 16:46:51

我正在处理第三方提供的 Active X 控件,因此无法更改 ActiveX 内部的处理例程。

我相信你是正确的,“COM 对象已与其底层 RCW 分离”问题是线程问题,而不是处置实现问题。 COM 与托管线程和托管内存相处得不好。

尝试这样的操作:

// Check to see if we need to use Invoke before wasting time with it
if (activexControl.InvokeRequired)
{
    // invoke Dispose on the control's native thread to avoid the COM exception
    activexControl.Invoke(() => activexControl.Dispose());
}
else
{
    // run dispose on this thread
    activexControl.Dispose();
}

调用是避免 COM 错误所需的全部内容,if 语句在不需要调用的情况下有助于提高性能。

请注意,创建活动 x 控件的线程需要仍然处于活动状态,否则您会遇到不同的问题。在您的精简代码中, main 在生成新线程后立即退出,并且当您稍后需要它时它可能不会处于活动状态。我一直在单元测试中遇到这种情况,并且必须在退出顶级函数之前插入等待其他线程结束的阻塞调用。

I'm dealing with an active x control provided by a third party, so changing the disposal routine inside ActiveX is not possible.

I believe you are correct that the "COM object that has been separated from its underlying RCW" issue is a threading issue, not a dispose implementation issue. COM just doesn't get along well with managed threads and managed memory.

Try something like this:

// Check to see if we need to use Invoke before wasting time with it
if (activexControl.InvokeRequired)
{
    // invoke Dispose on the control's native thread to avoid the COM exception
    activexControl.Invoke(() => activexControl.Dispose());
}
else
{
    // run dispose on this thread
    activexControl.Dispose();
}

The invoke is all that's needed to avoid the COM error, the if statement helps performance in cases where invoke is not required.

Do beware that the thread that created the active x control needs to still be alive, or you'll get different problems. In your stripped down code main exits right after you spawn the new thread and it won't likely be alive when you need it later. I run into this in unit testing all the time, and have to insert blocking calls that wait on the conclusion of other threads before exiting my top level function.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文