Pull to refresh

Comments 24

UFO just landed and posted this here

Финализаторы нужны, если вы работаете с неуправляемыми ресурсами (что само по себе крайне редкое явление, конечно). Например, выделяете неуправляемую память. Тогда наличие правильного финализатора гарантирует, что если вы сами не освободите такие ресурсы, потому что забыли using или что-то еще навернулось, то в итоге их освободит финализатор и утечек не будет.

UFO just landed and posted this here

А разве SafeHandle используется не исключительно для управления дескрипторами операционной системы? Если нужно работать со сторонними неуправляемыми ресурсами, то он же не подойдет? А то я на практике в этой "неуправляемой" области не работал, не особо ориентируюсь.
И как я понимаю, сам SafeHandle использует финализатор для своей работы. Так что в конечном итоге финализаторы нужны, чтобы сделать вещи вроде SafeHandle, которые позволяют не писать финализаторы самому )

SafeHandle работает с любым неуправляемым ресурсом который может быть представлен указателем (а таковых — большинство). Совершенно не обязательно чтобы этот ресурс был системным.

Кстати, чтобы сделать свой аналог SafeHandle — одного только финализатора недостаточно. Там еще есть особая логика маршалинга в P/Invoke, которая не дает потоку прерваться между получением дескриптора из неуправляемого кода и созданием управляемой обертки.

Разве? А msdn вот более строг: Represents a wrapper class for operating system handles. Как я понял из документации, там какая-то специфичная для дескрипторов ОС магия происходит, чтобы ресурс не поломался. Но могу ошибаться, конечно.

Ну, не первый раз в msdn неполную информацию пишут… Нет там никакого специфичного для дескрипторов ОС механизма.
UFO just landed and posted this here

Но тут не сказано, что это обязательно будет сделано ) Сказано, что это "помогает собрать сразу", а не "сразу приводит к сборке". Хотя в оригинале более точная формулировка, перевод искажает смысл:


The JIT and the GC are working together to track some auxiliary information that helps the GC to clean objects as soon as possible.
А на что это вообще может повлиять? Если методу не нужен объект, то ему в целом без разницы есть он или нет уже.
На любые внешние зависимости, вызываемые из финализатора. В основном проблемы с вызовами неуправляемого кода бывают.
Если объект инкапсулирует unmanaged ресурс, то кроме этого ресурса он ничем другим владеть не должен. Далее если на объект потеряли все ссылки, вызвав в параллельном потоке метод, который в середине своей работы потерял последнюю ссылку на объект (после последнего использования обнулен регистр, отвечающий за this), то ничего не мешает финализировать. Благо, сам объект никем не используется, а инкапсуляция unmanaged ресурса подразумевает что наружу ничего из unmanaged не выйдет. А значит не о чем беспокоиться. Приведите пример проблем, пожалуйста. Сценарий

Смотрите. Объект владеет некоторым ресурсом, который в данный момент используется — но на объект ссылок больше не осталось. Сборщик мусора вызывает финализатор для объекта, который освобождает ресурс. А ресурс в это время используется. Вот и ошибка.


Непонятно и нужен пример? Вот вам пример:


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.
Вы забыли сделать метод DoSomething неуправляемым и вызвать его через P/Invoke :-)

При чем тут это? Пример и без того демонстрирует все что надо

При том, что SafeHandle защищено от сборки не во время выполнения своих методов, а во время выполнения неуправляемых методов куда оно передано.
Вопрос в поведении, а не в хаках как от этого защититься ) Работа внутреннего счетчика ссылок — это и так ясно. Вопрос в том что приложение работает не так как задумано не потому что программист что-то не сделал а потому что GC делает неявную оптимизацию
А что именно работает не так как задумано, за исключением порядка строк в логе? :-) Общего ресурса-то в вашем примере нет.

Если бы ресурс был неуправляемым — то ошибки бы не было, так как SafeHandle защищен от сборки во время выполнения вызовов P/Invoke.

Если бы ресурс был, но управляемый — то сработала бы другая рекомендация, не освобождать управляемые ресурсы в финализаторах (ибо у них свой финализатор есть если очень нужно).

KeepAlive спасает, да, но ппц, имхо

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

Sign up to leave a comment.

Articles