Утилизация ресурсов ActiveX, принадлежащих другому потоку - PullRequest
0 голосов
/ 16 июня 2010

У меня проблема с потоками и утилизацией ресурсов.

У меня есть приложение C # Windows Forms, которое выполняет дорогостоящую операцию в потоке. Этот поток создает экземпляр элемента управления ActiveX (AxControl). Этот элемент управления должен быть удален, поскольку он использует большой объем памяти. Поэтому я реализовал метод Dispose () и даже деструктор.

После завершения потока вызывается деструктор. К сожалению, это называется потоком пользовательского интерфейса. Так что вызывая 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
    }
}

Ответы [ 3 ]

2 голосов
/ 16 июня 2010

Мне не ясно, что вы подразумеваете под элементом управления ActiveX, который реализует метод Dispose. Шаблон IDisposable предназначен для управляемого кода. Чтобы освободить COM-объект, вы обычно используете Marshal.ReleaseComObject - возможно, вы делаете это внутри вашего AxControl класса, реализацию которого вы не показываете.

Несколько вещей не так с приведенным выше кодом.

Вы должны удалить экземпляр IDisposable DoStuff:

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

Вы не должны получать доступ к управляемым ресурсам в финализаторе - в вашем случае финализатор вызывает Dispose, который затем ссылается на управляемый экземпляр axControl. Возможно, этот экземпляр уже был собран к моменту запуска финализатора.

Поскольку вы не используете неуправляемые ресурсы непосредственно в классе DoStuff, вам, вероятно, не нужен финализатор, но если он у вас есть, следуйте стандартному шаблону IDisposable в MSDN и не пытайтесь избавиться от каких-либо управляемых объектов. .

UPDATE

Комментарий:

AxControl - это библиотека .NET Interop Wrapper DLL, сгенерированная Visual Studio.

В этом случае, что такое метод Dispose ()? Я не понимаю, почему вы бы реализовали такой метод в элементе управления ActiveX, который имеет детерминированную финализацию - обычно вы выполняете очистку после освобождения последней ссылки COM.

Ваш метод DoStuff.Dispose может захотеть освободить COM-объект, например,

public void Dispose()  
{  
    activexControl.Dispose();  
    Marshal.ReleaseComObject(activexControl);
}
1 голос
/ 16 июня 2010

Почему рабочий поток не удаляет его явно?

Вы можете изменить

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

К

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

Так что вам даже не нужно об этом беспокоиться.

0 голосов
/ 09 января 2013

Я имею в виду активный элемент управления 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, должен оставаться живым, иначе возникнут другие проблемы. В вашем урезанном коде основные выходы сразу после того, как вы создали новый поток, и он вряд ли будет жив, когда вам это понадобится позже. Я постоянно сталкиваюсь с этим в модульном тестировании, и мне приходится вставлять блокирующие вызовы, которые ждут завершения других потоков перед выходом из функции верхнего уровня.

...