Финализаторы и утилизация

У меня есть класс с именем BackgroundWorker у которого есть нить, постоянно работающая. Чтобы отключить этот поток, переменная экземпляра с именем stop чтобы быть должно быть true,

Чтобы убедиться, что поток освобождается, когда класс используется, я добавил IDisposable и финализатор, который вызывает Dispose(), При условии, что stop = true действительно ли это приводит к выходу из этой ветки? Нормально вызывать Dispose из финализатора, верно?

Финализаторы всегда должны звонить Dispose если object наследуется IDisposable, право?

/// <summary>
/// Force the background thread to exit.
/// </summary>
public void Dispose()
{
    lock (this.locker)
    {
        this.stop = true;
    }
}

~BackgroundWorker()
{
    this.Dispose();
}

6 ответов

Решение

Ваш код в порядке, хотя блокировка в финализаторе несколько "страшна", и я бы этого не допустил - если вы зашли в тупик... Я не уверен на 100%, что произойдет, но это не будет хорошо. Однако, если вы в безопасности, это не должно быть проблемой. В основном. Внутренние элементы сборки мусора болезненны, и я надеюсь, что вам никогда не придется их видеть;)

Как отмечает Марк Гравелл, изменчивый бул позволит вам избавиться от блокировки, что уменьшит эту проблему. Реализуйте это изменение, если можете.

Код nedruod помещает назначение в проверку if (распоряжения), что совершенно неверно - поток является неуправляемым ресурсом и должен быть остановлен, даже если он не был явно уничтожен. Ваш код в порядке, я просто указываю, что вы не должны следовать советам, приведенным в этом фрагменте кода.

Да, вы почти всегда должны вызывать Dispose() из финализатора при реализации шаблона IDisposable. Полный шаблон IDisposable немного больше, чем у вас, но он вам не всегда нужен - он просто предоставляет две дополнительные возможности:

  1. определение, был ли вызван Dispose() или выполняется финализатор (вам не разрешается касаться каких-либо управляемых ресурсов в финализаторе, за исключением завершаемого объекта);
  2. включение подклассов для переопределения метода Dispose().

Прежде всего, серьезное предупреждение. Не используйте финализатор, как вы. Вы настраиваете себя на очень плохие эффекты, если вы берете блокировки в финализаторе. Короткая история не делай этого. Теперь к оригинальному вопросу.

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

/// <summary>
/// Force the background thread to exit.
/// </summary>
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        lock (this.locker)
        {
            this.stop = true;
        }
    }
}

~BackgroundWorker()
{
    Dispose(false);
}

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

Из интереса, по какой причине это не может использовать обычный BackgroundWorker, который имеет полную поддержку отмены?

Re the lock - изменчивое поле bool может быть менее проблематичным.

Тем не менее, в этом случае ваш финализатор не делает ничего интересного, особенно с учетом "если (удаление)" - т.е. он выполняет только интересный код во время Dispose(). Лично у меня будет соблазн придерживаться только IDisposable и не предоставлять финализатор: вы должны очистить его с помощью Dispose().

Вам нужен полный одноразовый шаблон, но стоп должен быть тем, к чему может получить доступ нить. Если это переменная-член располагаемого класса, это не хорошо, потому что она не может ссылаться на удаленный класс. Подумайте о том, чтобы иметь событие, которым владеет поток, и вместо этого сигнализировать об этом.

Является ли переменная экземпляра "stop" свойством? Если нет, то нет особого смысла устанавливать его во время финализатора - ничто больше не ссылается на объект, поэтому ничто не может запросить член.

Если вы на самом деле освобождаете ресурс, то использование Dispose() и финализатора выполняет одну и ту же работу (сначала проверяя, нужно ли еще выполнять эту работу), является хорошим примером.

Объект, который реализует финализатор, нуждается в ссылке на флаг - сохраненный в другом объекте - который поток сможет видеть; поток не должен иметь никаких прямых или косвенных ссылок на объект, который реализует финализатор. Финализатор должен установить флаг, используя что-то вроде CompareExchange, а поток должен использовать аналогичные средства для его проверки. Обратите внимание, что если финализатор одного объекта обращается к другому объекту, другой объект, возможно, был завершен, но он все еще будет существовать. Финализатор может ссылаться на другие объекты, если он делает это так, чтобы их финализация не беспокоила. Если все, что вы делаете, это устанавливаете флаг, у вас все в порядке.

Другие вопросы по тегам