Комментарии 24
(del, здесь было ошибочное рассуждение)
Финализаторы нужны, если вы работаете с неуправляемыми ресурсами (что само по себе крайне редкое явление, конечно). Например, выделяете неуправляемую память. Тогда наличие правильного финализатора гарантирует, что если вы сами не освободите такие ресурсы, потому что забыли using или что-то еще навернулось, то в итоге их освободит финализатор и утечек не будет.
А разве SafeHandle используется не исключительно для управления дескрипторами операционной системы? Если нужно работать со сторонними неуправляемыми ресурсами, то он же не подойдет? А то я на практике в этой "неуправляемой" области не работал, не особо ориентируюсь.
И как я понимаю, сам SafeHandle использует финализатор для своей работы. Так что в конечном итоге финализаторы нужны, чтобы сделать вещи вроде SafeHandle, которые позволяют не писать финализаторы самому )
Кстати, чтобы сделать свой аналог SafeHandle — одного только финализатора недостаточно. Там еще есть особая логика маршалинга в P/Invoke, которая не дает потоку прерваться между получением дескриптора из неуправляемого кода и созданием управляемой обертки.
Разве? А msdn вот более строг: Represents a wrapper class for operating system handles. Как я понял из документации, там какая-то специфичная для дескрипторов ОС магия происходит, чтобы ресурс не поломался. Но могу ошибаться, конечно.
Но тут не сказано, что это обязательно будет сделано ) Сказано, что это "помогает собрать сразу", а не "сразу приводит к сборке". Хотя в оригинале более точная формулировка, перевод искажает смысл:
The JIT and the GC are working together to track some auxiliary information that helps the GC to clean objects as soon as possible.
Смотрите. Объект владеет некоторым ресурсом, который в данный момент используется — но на объект ссылок больше не осталось. Сборщик мусора вызывает финализатор для объекта, который освобождает ресурс. А ресурс в это время используется. Вот и ошибка.
Непонятно и нужен пример? Вот вам пример:
class Foo
{
IntPtr handle;
public Foo() { handle = CreateUnmanagedObject(); }
public Run() { RunUnmanagedObject(handle); } // Вот тут будет беда если в другом потоке параллельно отработает финализатор.
~Foo() { DisposeUnmanagedObject(handle); }
}
Кстати, да. Согласен. Мало того, срабатывает и на таком методе:
void CheckReachability()
{
var weakRef = new WeakReference(this);
Console.WriteLine("Calling GC.Collect...");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
string message = weakRef.IsAlive ? "alive" : "dead";
Console.WriteLine("Object is " + message);
}
Т.е., получается, надо как-то фейково использовать this… после реального чтобы он не был вдруг собран… ) ппц..
Так есть же GC.KeepAlive
, который как раз фейково использует аргумент продлевая тем самым ему время жизни.
А еще есть HandleRef
, который не дает собрать объект во время вызова внешнего метода через P/Invoke.
Ну и, наконец, есть SafeHandle
. Если взять за правило всегда оборачивать любые неуправляемые ресурсы в SafeHandle
— можно забыть про любые GC.KeepAlive или HandleRef.
Так что подобные посты следует рассматривать не как рассказы "что нужно знать каждому C#-программисту", а больше как рассказы "что будет если вы решите отказаться от SafeHandle" :-)
Не совсем:
internal class GcIsWeird : SafeHandle
{
public GcIsWeird() : base(IntPtr.Zero, true)
{
}
~GcIsWeird()
{
Console.WriteLine("Finalizing instance.");
}
public int data = 42;
public override bool IsInvalid => false;
public void DoSomething()
{
Console.WriteLine("Doing something. The answer is ... " + data);
CheckReachability();
Console.WriteLine("Finished doing something.");
}
void CheckReachability()
{
Console.WriteLine("Calling GC.Collect...");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
protected override bool ReleaseHandle()
{
Console.WriteLine("Releasing handle");
return true;
}
}
static void Main(string[] args)
{
var s = new GcIsWeird();
s.DoSomething();
}
-->
Doing something. The answer is ... 42
Calling GC.Collect...
Finalizing instance.
Releasing handle
Finished doing something.
При чем тут это? Пример и без того демонстрирует все что надо
Если бы ресурс был неуправляемым — то ошибки бы не было, так как SafeHandle защищен от сборки во время выполнения вызовов P/Invoke.
Если бы ресурс был, но управляемый — то сработала бы другая рекомендация, не освобождать управляемые ресурсы в финализаторах (ибо у них свой финализатор есть если очень нужно).
KeepAlive спасает, да, но ппц, имхо
Этот пример не работает, думаю там умнее эвристика. Сломаться может только если вы полагаетесь на временной фактор или методы косвенно полагаются на доступность ресурса или какая-либо многопоточная магия дергается из финализатора.
Сборка мусора и время жизни объектов