Почему я не видел никаких реализаций IDisposable, реализующего параллелизм? - PullRequest
15 голосов
/ 14 марта 2011

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

Ответы [ 4 ]

30 голосов
/ 14 марта 2011

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

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

6 голосов
/ 14 марта 2011

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

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

2 голосов
/ 14 марта 2011

Брайан Ламберт написал сообщение в блоге под названием Простая и полностью поточно-ориентированная реализация 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);
}

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

1 голос
/ 14 марта 2011

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

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

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

...