Подтвердить что ты не робот

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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