Подтвердить что ты не робот

Почему я не видел каких-либо реализаций реализации IDisposable concurrency?

Когда я просматриваю примеры реализации IDisposable, я не нашел ни одного из них, которые являются потокобезопасными. Почему IDisposable не применяется для обеспечения безопасности потоков? (Вместо этого вызывающие абоненты несут ответственность за то, чтобы только один поток вызывал Dispose()).

4b9b3361

Ответ 1

Вы не должны уничтожать объект, пока не закончите с ним. Если есть другие темы, ссылающиеся на объект, и есть вероятность, что они могут захотеть вызвать его методы, вы не должны избавляться от него.

Поэтому для Dispose не обязательно быть потокобезопасным.

Ответ 2

Единственное реальное преимущество для поточно-безопасного шаблона Dispose заключается в том, что вам может быть гарантировано получить исключение ObjectDisposedException, а не потенциально непредсказуемое поведение в случае неправильного использования перекрестных ссылок. Обратите внимание, что это означает, что шаблон требует больше, чем поточно-безопасный Dispose; это требует, чтобы все методы, которые полагаются на класс, не являющийся "Расположенным", блокируются должным образом с механизмом удаления.

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

Ответ 3

Брайан Ламберт написал запись в блоге Простая и полностью потокобезопасная реализация IDisposable.
Он содержит следующую реализацию:

using System;
using System.Threading;

/// <summary>
/// DisposableBase class. Represents an implementation of the IDisposable interface.
/// </summary>
public abstract class DisposableBase : IDisposable
{
    /// <summary>
    /// A value which indicates the disposable state. 0 indicates undisposed, 1 indicates disposing
    /// or disposed.
    /// </summary>
    private int disposableState;

    /// <summary>
    /// Finalizes an instance of the DisposableBase class.
    /// </summary>
    ~DisposableBase()
    {
        // The destructor has been called as a result of finalization, indicating that the object
        // was not disposed of using the Dispose() method. In this case, call the DisposeResources
        // method with the disposeManagedResources flag set to false, indicating that derived classes
        // may only release unmanaged resources.
        this.DisposeResources(false);
    }

    /// <summary>
    /// Gets a value indicating whether the object is undisposed.
    /// </summary>
    public bool IsUndisposed
    {
        get
        {
            return Thread.VolatileRead(ref this.disposableState) == 0;
        }
    }

    #region IDisposable Members

    /// <summary>
    /// Performs application-defined tasks associated with disposing of resources.
    /// </summary>
    public void Dispose()
    {
        // Attempt to move the disposable state from 0 to 1. If successful, we can be assured that
        // this thread is the first thread to do so, and can safely dispose of the object.
        if (Interlocked.CompareExchange(ref this.disposableState, 1, 0) == 0)
        {
            // Call the DisposeResources method with the disposeManagedResources flag set to true, indicating
            // that derived classes may release unmanaged resources and dispose of managed resources.
            this.DisposeResources(true);

            // Suppress finalization of this object (remove it from the finalization queue and
            // prevent the destructor from being called).
            GC.SuppressFinalize(this);
        }
    }

    #endregion IDisposable Members

    /// <summary>
    /// Dispose resources. Override this method in derived classes. Unmanaged resources should always be released
    /// when this method is called. Managed resources may only be disposed of if disposeManagedResources is true.
    /// </summary>
    /// <param name="disposeManagedResources">A value which indicates whether managed resources may be disposed of.</param>
    protected abstract void DisposeResources(bool disposeManagedResources);
}

Однако абсолютная и полная совокупность немного оспаривается в комментариях, как в блоге, так и здесь.

Ответ 4

Я не уверен, почему Microsoft не использует флаг блокировки Disposing в методе не виртуального размещения (с намерением, чтобы финализатор, если он есть, использовал тот же флаг). Ситуации, когда несколько потоков могут пытаться уничтожить объект, редки, но это не запрещено. Это может произойти, например, с объектами, которые должны выполнять некоторую асинхронную задачу и очищаться после себя, но которые могут быть уничтожены раньше, если это необходимо. Устранение объектов не должно происходить достаточно часто, чтобы Interlocked.Exchange имел какие-либо значимые эксплуатационные расходы.

С другой стороны, важно отметить, что при защите Dispose от множественных вызовов это IMHO мудрая политика, недостаточно сделать Dispose действительно потокобезопасной. Также необходимо убедиться, что вызов Dispose на объект, который используется, оставит все в хорошем состоянии. Иногда лучшим примером для этого является то, что он должен установить флаг "KillMeNow", а затем в блоке, защищенном Monitor.TryEnter, "Утилизировать объект". Каждая процедура (кроме Dispose), которая использует объект, должна приобретать блокировку во время работы, но проверяйте оба перед приобретением и после освобождения блокировки, чтобы увидеть, установлен ли KillMeNow; если это так, выполните Monitor.TryEnter и выполните логику удаления.

Большая проблема с созданием потокобезопасного IDisposable заключается в том, что Microsoft не указывает, что метод RemoveHandler для событий должен быть потокобезопасным без риска возникновения взаимоблокировки. Это часто необходимо для IDisposable. Рекомендуется удалить обработчики событий; без гарантированного по потоку способа сделать это, практически невозможно написать потокобезопасный Dispose.

Ответ 5

Потому что они не правы и ленивы, их часто называют "крайними случаями" или "не стоит накладных расходов" за правильность.

Используйте блокировку чтения/записи для синхронизации одноразового класса. Вещество, которое вы защищаете от одновременных записей, - это "удаленное" состояние. Все методы просто получают блокировку чтения для использования класса, что гарантирует, что они не расположены в середине вызова. Поскольку поддерживается несколько считывателей, методы не блокируют друг друга. Метод "dispose" должен получить блокировку записи для выполнения очистки, гарантируя, что он не будет работать одновременно ни с каким другим методом или с самим собой.

Это обеспечивает полную безопасность потоков для метода dispose и всех других методов в классе. Логика утилизации не будет работать, пока работает нормальная логика (и наоборот), а также не будет работать одновременно с самим собой.

Используемая вами блокировка чтения-записи должна поддерживать асинхронность, поэтому вы можете использовать асинхронный код. В качестве бонуса асинхронные блокировки обычно не поддерживают повторный вход. Это хорошо, потому что вы не захотите, чтобы один метод вызывал другой в любом случае, так как между двумя вызовами получения блокировки чтения может произойти блокировка "записи" и предотвратить бесконечное снятие второй блокировки чтения (взаимоблокировка).

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