Безопасное освобождение ссылки на COM-объект из .NET
-
11-09-2019 - |
Вопрос
Я прочитал много статей в сети о безопасном выпуске RCW, и мне кажется, что никто не может прийти к единому мнению, что именно и в каком порядке нужно делать, поэтому я прошу вас, ребята, высказать свое мнение.Например, можно было бы сделать это:
object target = null;
try {
// Instantiate and use the target object.
// Assume we know what we are doing: the contents of this try block
// do in fact represent the entire desired lifetime of the COM object,
// and we are releasing all RCWs in reverse order of acquisition.
} finally {
if(target != null) {
Marshal.FinalReleaseComObject(target);
target = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Тем не менее, некоторые люди рекомендуют выполнять сбор мусора перед Marshal.FinalReleaseComObject
, кто-то после, а кто-то вообще нет.Действительно ли необходимо вручную собирать каждый RCW, особенно после того, как он уже отсоединен от своего COM-объекта?
На мой взгляд, было бы проще и проще просто отсоединить RCW от COM-объекта и оставить срок действия RCW естественным образом:
object target = null;
try {
// Same content as above.
} finally {
if(target != null) {
Marshal.FinalReleaseComObject(target);
}
}
Достаточно ли этого сделать?
Решение
Чтобы освободить ссылку на целевой COM-объект, достаточно и предпочтительный просто позвонить Marshal.FinalReleaseComObject
и нет заставить собирать.Другими словами, вы выполнили свою обязанность опубликовать ссылку, как только закончите с ней.Я не буду касаться вопроса FinalReleaseComObject
против ReleaseComObject
.
Это оставляет более серьезный вопрос: почему люди выступают за звонки GC.Collect()
и WaitForPendingFinalizers()
?
Поскольку в некоторых проектах трудно определить, что управляемых ссылок больше нет, и вы не можете безопасно вызвать ReleaseComObject
.У вас есть два варианта: позволить памяти накопиться и надеяться, что сбор произойдет, или принудительно собрать сбор.[см. комментарий Стивена Янсена о голосовании против]
Дополнительное примечание: настройка target
к null
обычно не требуется, и особенно в вашем примере кода.Обнуление объектов является обычной практикой для VB6, поскольку он использует сборщик мусора на основе счетчика ссылок.Компилятор C# достаточно умен (при сборке для выпуска), чтобы знать, что target
недоступен после его последнего использования и может быть GC'd, даже до выхода из области действия.Под последним использованием я подразумеваю последнее возможное использование, поэтому в некоторых случаях вы можете установить его на null
.Вы можете убедиться в этом сами с помощью кода ниже:
using System;
class GCTest
{
~GCTest() { Console.WriteLine("Finalized"); }
static void Main()
{
Console.WriteLine("hello");
GCTest x = new GCTest();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("bye");
}
}
Если вы создаете выпуск (например, CSC GCTest.cs), «Finalized» будет печататься между «привет» и «пока».Если вы создаете отладочную версию (например, CSC /debug GCTest.cs), «Finalized» будет распечатано после «bye», тогда как установка x
до нуля до Collect()
бы это "исправило".