Представьте себе реализацию интерфейса IDisposable
, у которого есть некоторые общедоступные методы.
Если экземпляр этого типа используется совместно несколькими потоками и один из потоков может его удалить, как лучше всего гарантировать, что другие потоки не будут пытаться работать с экземпляром после удаления? В большинстве случаев после удаления объекта его методы должны знать об этом и генерировать исключение ObjectDisposedException
или, возможно, InvalidOperationException
или, по крайней мере, сообщить вызывающему коду о том, что он сделал что-то неправильно. Нужна ли мне синхронизация для каждого метода - особенно вокруг проверки, если он удален? Все ли реализации IDisposable
с другими общедоступными методами должны быть потокобезопасными?
Вот пример:
public class DummyDisposable : IDisposable
{
private bool _disposed = false;
public void Dispose()
{
_disposed = true;
// actual dispose logic
}
public void DoSomething()
{
// maybe synchronize around the if block?
if (_disposed)
{
throw new ObjectDisposedException("The current instance has been disposed!");
}
// DoSomething logic
}
public void DoSomethingElse()
{
// Same sync logic as in DoSomething() again?
}
}
Большинство реализаций BCL в Dispose не являются поточно-ориентированными. Идея состоит в том, что вызывающий Dispose должен убедиться, что никто больше не использует этот экземпляр до его удаления. Другими словами, это увеличивает ответственность за синхронизацию. Это имеет смысл, так как в противном случае теперь всем другим вашим потребителям нужно обрабатывать граничный случай, когда объект был удален, пока они его использовали.
Тем не менее, если вам нужен потокобезопасный класс Disposable, вы можете просто создать блокировку вокруг каждого открытого метода (включая Dispose) с проверкой на _disposed вверху. Это может усложниться, если у вас есть долгосрочные методы, в которых вы не хотите удерживать блокировку для всего метода.
Я предпочитаю использовать целые числа и Interlocked.Exchange
или Interlocked.CompareExchange
в объекте типа integer типа «disposed» или «state»; Я бы использовал enum
, если бы Interlocked.Exchange
или Interlocked.CompareExchange
могли обрабатывать такие типы, но, увы, они не могут.
Одна вещь, о которой большинство дискуссий об IDisposable и финализаторах не упоминается, это то, что, хотя финализатор объекта не должен запускаться, пока IDisposable.Dispose () находится в процессе, у класса нет способа предотвратить объявление объектов его типа. умер, а затем воскрес. Конечно, если внешний код допускает это, очевидно, не может быть никаких требований, чтобы объект «работал нормально», но методы Dispose и finalize должны быть достаточно хорошо защищены, чтобы не повредить их . ] состояние других объектов, которое, в свою очередь, обычно требует использования либо блокировок, либо Interlocked
операций с переменными состояния объекта.