Безопасно ли вызывать RCW из финализатора?

У меня есть управляемый объект, который вызывает COM-сервер для выделения некоторой памяти. Управляемый объект должен снова вызвать COM-сервер, чтобы освободить эту память, прежде чем управляемый объект уйдет, чтобы избежать утечки памяти. Этот объект реализует IDisposable, чтобы гарантировать выполнение правильного освобождающего память COM-вызова.

В случае, если метод Dispose не вызван, я бы хотел, чтобы финализатор объекта освободил память. Проблема в том, что правила финализации заключаются в том, что вы не должны обращаться к какой-либо ссылке, потому что вы не знаете, какие другие объекты уже были GC'd и / или финализированы до вас. При этом единственным доступным для прикосновения состоянием объекта остаются поля (наиболее распространены дескрипторы).

Но вызов COM-сервера включает прохождение вызываемой оболочки времени выполнения (RCW), чтобы освободить память, в которой у меня есть файл cookie, который хранится в поле. Безопасно ли вызывать этот RCW из финализатора (гарантируется ли, что на этом этапе он не был GC'd или финализирован)?

Для тех из вас, кто не знаком с финализацией, хотя поток финализатора работает в фоновом режиме управляемого домена приложения во время его работы, в этих случаях касание ссылок теоретически будет нормально, финализация также происходит при выключении домена приложения и в любом порядке - не только в порядке ссылочных отношений. Это ограничивает то, к чему вы можете безопасно прикасаться из финализатора. Любая ссылка на управляемый объект может быть «плохой» (собранная память), даже если ссылка не равна нулю.

Обновление: я только что попробовал и получил следующее:

Необработанное исключение типа System.Runtime.InteropServices.InvalidComObjectException произошло в myassembly.dll

Дополнительная информация: COM-объект, который был отделен от лежащего в его основе RCW, использовать нельзя.


person Andrew Arnott    schedule 15.10.2009    source источник
comment
Метод на COM-сервере (зачем кому-то вызывать Dispose на самом RCW? Я был бы удивлен, если это вообще возможно).   -  person Andrew Arnott    schedule 15.10.2009


Ответы (2)


Я узнал от самой команды CLR, что это действительно небезопасно - если вы не выделяете GCHandle на RCW, пока это все еще безопасно (когда вы впервые приобретаете RCW). Это гарантирует, что GC и финализатор не сумели RCW до того, как управляемый объект, который должен его вызвать, будет финализирован.

class MyManagedObject : IDisposable
{
    private ISomeObject comServer;
    private GCHandle rcwHandle;
    private IServiceProvider serviceProvider;
    private uint cookie;

    public MyManagedObject(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
        this.comServer = this. serviceProvider.GetService(/*some service*/) as ISomeObject;
        this.rcwHandle = GCHandle.Alloc(this.comServer, GCHandleType.Normal);
        this.cookie = comServer.GetCookie();
    }

    ~MyManagedObject()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // dispose owned managed objects here.
        }

        if (this.rcwHandle.IsAllocated)
        {
            // calling this RCW is safe because we have a GC handle to it.
            this.comServer.ReleaseCookie(this.cookie);

            // Now release the GC handle on the RCW so it can be freed as well
            this.rcwHandle.Free();
        }
    }
}

Оказывается, в моем частном случае мое приложение размещает саму среду CLR. Следовательно, он вызывает mscoree! CoEEShutdownCOM до того, как поток финализатора запускается, что убивает RCW и приводит к ошибке InvalidComObjectException, которую я видел.

Но в обычных случаях, когда CLR не размещает себя, мне говорят, что это должно сработать.

person Andrew Arnott    schedule 15.10.2009
comment
Привет, Эндрю, спасибо за этот пост. Я пытаюсь применить предложенное вами решение к классу, охватывающему IAudioSessionControl2 COM-интерфейс. Класс реализует IDisposable интерфейс, вызывает Marshal.ReleaseComObject через IAudioSessionControl2 экземпляр-интерфейса, что приводит к ошибке, на которую вы ссылаетесь в своем сообщении. Приступая к применению вашего предложения, я заменил ISomeObject (с которым я не знаком) на object, но не знаю, как использовать аргумент serviceProvider конструктора и какую услугу следует указать при вызове GetService? Весьма признателен. - person Bliss; 02.03.2014

Нет, доступ к RCW из потока финализатора небезопасен. Как только вы достигнете потока финализатора, у вас нет гарантии, что RCW все еще жив. Возможно, он будет впереди вашего объекта в очереди финализатора и, следовательно, будет освобожден к тому времени, когда ваш деструктор будет запущен в потоке финализатора.

person JaredPar    schedule 15.10.2009
comment
Мой ответ удален, поскольку в моем случае мне повезло с веткой финализатора. - person user7116; 15.10.2009