Потокобезопасность утилизации методов? - PullRequest
8 голосов
/ 17 февраля 2011

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

Является Dispose метод а) гарантированно поточно-ориентированный для всех классов, б) никогда не гарантированно поточно-ориентированный, в) гарантированный поточно-ориентированный для некоторых классов (если так, где это конкретно задокументировано)?

Наконец, если метод Dispose гарантированно является потокобезопасным, означает ли это, что я должен установить блокировку вокруг каждого метода экземпляра в классе, который использует доступные ресурсы?

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

Ответы [ 3 ]

6 голосов
/ 17 февраля 2011

Вопрос безопасности потоков и утилизации несколько сложен.Поскольку во многих случаях единственное, что любой поток может законно сделать с объектом после того, как любой другой поток начал распоряжаться им, это попытка удалить его самостоятельно, на первый взгляд кажется, что единственное, что необходимо для обеспечения безопасности потока, этоиспользуйте Interlocked.Exchange для флага 'disposed', чтобы убедиться, что попытка Dispose одного потока происходит, а другой игнорируется.Действительно, это хорошая отправная точка, и я думаю, что она должна была быть частью стандартного шаблона Dispose (CompareExchange должен был быть выполнен в запечатанном методе-оболочке базового класса, чтобы избежать необходимости для каждого производного класса использовать свой собственныйраспоряжаться флагом).К сожалению, если учесть, что на самом деле делает Dispose, все гораздо сложнее.

Истинная цель Dispose - не сделать что-то для объекта, который должен быть удален, а скорее очистить другие объекты, которые этот объект содержитРекомендации.Эти объекты могут быть управляемыми объектами, системными объектами или чем-то еще целиком;они могут даже не находиться на том же компьютере, что и находящийся объект.Для Dispose, чтобы быть потокобезопасным, эти другие объекты позволили бы Dispose очистить их в то же время, как другие потоки могут делать с ними другие вещи.Некоторые объекты могут справиться с таким использованием;другие не могут.

Один конкретный неприятный пример: объектам разрешено иметь события с методами RemoveHandler, которые не являются поточно-ориентированными.Следовательно, любой метод Dispose, который очищает обработчики событий, должен вызываться только из того же потока, в котором создавались подписки.

2 голосов
/ 17 февраля 2011

Я вполне уверен, что, если не указано иное, метод Dispose() любого класса будет считаться «элементом экземпляра» для целей документации, указывающей на безопасность потока или нет.

Так, если документациязаявляет, что члены экземпляра не являются потокобезопасными, тогда и Dispose() не обязательно будет, если только это не указано особо как отличное от остальных.

2 голосов
/ 17 февраля 2011

На странице MSDN здесь фактически никогда прямо не говорится, что методы Dispose не являются поточно-ориентированными, но, насколько я понимаю, их код подразумевает, что нет, они не являются поточно-ориентированными, и вы должны учитывать это при необходимости.

Конкретно комментарии в примере кода:

// This class shows how to use a disposable resource.
// The resource is first initialized and passed to
// the constructor, but it could also be
// initialized in the constructor.
// The lifetime of the resource does not 
// exceed the lifetime of this instance.
// This type does not need a finalizer because it does not
// directly create a native resource like a file handle
// or memory in the unmanaged heap.

public class DisposableResource : IDisposable
{

    private Stream _resource;  
    private bool _disposed;

    // The stream passed to the constructor 
    // must be readable and not null.
    public DisposableResource(Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException("Stream in null.");
        if (!stream.CanRead)
            throw new ArgumentException("Stream must be readable.");

        _resource = stream;

        _disposed = false;
    }

    // Demonstrates using the resource. 
    // It must not be already disposed.
    public void DoSomethingWithResource() {
        if (_disposed)
            throw new ObjectDisposedException("Resource was disposed.");

        // Show the number of bytes.
        int numBytes = (int) _resource.Length;
        Console.WriteLine("Number of bytes: {0}", numBytes.ToString());
    }


    public void Dispose() 
    {
        Dispose(true);

        // Use SupressFinalize in case a subclass
        // of this type implements a finalizer.
        GC.SuppressFinalize(this);      
    }

    protected virtual void Dispose(bool disposing)
    {
        // If you need thread safety, use a lock around these 
        // operations, as well as in your methods that use the resource.
        if (!_disposed)
        {
            if (disposing) {
                if (_resource != null)
                    _resource.Dispose();
                    Console.WriteLine("Object disposed.");
            }

            // Indicate that the instance has been disposed.
            _resource = null;
            _disposed = true;   
        }
    }
}
...