Безопасно ли вызывать RCW из финализатора?
У меня есть управляемый объект, который вызывает COM-сервер для выделения некоторой памяти. Управляемый объект должен снова вызвать COM-сервер, чтобы освободить эту память до того, как управляемый объект уйдет, чтобы избежать утечки памяти. Этот объект реализует IDisposable
, чтобы гарантировать, что будет выполнен правильный COM-вызов, освобождающий память.
В случае, если метод Dispose
не вызывается, я хотел бы, чтобы финализатор объекта освободил память. Беда в том, что правила финализации заключаются в том, что вы не должны обращаться к какой-либо ссылке, потому что не знаете, какие другие объекты уже были GC'd и/или финализированы до вас. Это оставляет единственное осязаемое состояние объекта, которое должно быть полем (ручки являются наиболее распространенными).
Но вызов COM-сервера включает в себя выполнение оболочки, вызываемой вызовом (RCW), чтобы освободить память, в которой у меня есть файл cookie для сохранения в поле. Безопасен ли RCW безопасно звонить из финализатора (гарантировано ли он не был GC'd или финализирован в этой точке)?
Для тех из вас, кто не знаком с финализацией, хотя поток финализатора работает на фоне управляемого приложения, пока его запуск, для тех случаев, когда касания ссылок теоретически бывают в порядке, финализация также происходит при завершении appdomain и в любом порядке - не только в порядке ссылки. Это ограничивает то, что вы можете предположить, безопасно касаться вашего финализатора. Любая ссылка на управляемый объект может быть "плохой" (собранной памятью), даже если ссылка не равна нулю.
Обновление: я просто попробовал и получил следующее:
Необработанное исключение типа "System.Runtime.InteropServices.InvalidComObjectException" произошло в myassembly.dll
Дополнительная информация: COM-объект, который был отделен от его базового RCW, не может быть использован.
Ответы
Ответ 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 все еще жив. Он может быть впереди вашего объекта в очереди финализатора и, следовательно, освобожден к тому моменту, когда ваш деструктор работает в потоке финализатора.