valida una tuvo traves thread subprocesos subproceso sirve que para operacion net make llamó interfaz how form false diferente cross control checkforillegalcrossthreadcalls calls aplicación aplanó acceso c# .net thread-safety idisposable

una - thread c# windows form



¿Cuál es la forma correcta de agregar seguridad de subprocesos a un objeto IDisposable? (6)

Debe bloquear todos los accesos al recurso que vaya a disponer. También agregué el patrón de Disposición que normalmente uso.

public class MyThreadSafeClass : IDisposable { private readonly object lockObj = new object(); private MyRessource myRessource = new MyRessource(); public void DoSomething() { Data data; lock (lockObj) { if (myResource == null) throw new ObjectDisposedException(""); data = myResource.GetData(); } // Do something with data } public void DoSomethingElse(Data data) { // Do something with data lock (lockObj) { if (myRessource == null) throw new ObjectDisposedException(""); myRessource.SetData(data); } } ~MyThreadSafeClass() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { if (disposing) { lock (lockObj) { if (myRessource != null) { myRessource.Dispose(); myRessource = null; } } //managed ressources } // unmanaged ressources } }

Imagine una implementación de la interfaz IDisposable , que tiene algunos métodos públicos.

Si una instancia de ese tipo se comparte entre varios subprocesos y uno de los subprocesos puede eliminarlo, ¿cuál es la mejor manera de asegurarse de que los otros subprocesos no intenten trabajar con la instancia después de eliminarse? En la mayoría de los casos, después de eliminar el objeto, sus métodos deben ObjectDisposedException y lanzar la ObjectDisposedException o tal vez InvalidOperationException o al menos informar al código de llamada para hacer algo incorrecto. ¿Necesito sincronización para todos los métodos, especialmente en torno a la verificación si se desecha? ¿Todas IDisposable implementaciones IDisposable con otros métodos públicos deben ser seguras para subprocesos?

Aquí hay un ejemplo:

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? } }


FWIW, su código de muestra coincide con la forma en que mis compañeros de trabajo y yo tratamos este problema. Generalmente definimos un método CheckDisposed privado en la clase:

private volatile bool isDisposed = false; // Set to true by Dispose private void CheckDisposed() { if (this.isDisposed) { throw new ObjectDisposedException("This instance has already been disposed."); } }

Luego llamamos al método CheckDisposed() en la parte superior de todos los métodos públicos.

Si la contención de subprocesos sobre la eliminación se considera probable, en lugar de una condición de error, también IsDisposed() método IsDisposed() público (similar a Control.IsDisposed ).

Actualización: Basado en los comentarios con respecto al valor de hacer que isDisposed volátil, tenga en cuenta que el problema de la "cerca" es bastante trivial dado que uso el método CheckDisposed() . Es esencialmente una herramienta de solución de problemas para detectar rápidamente el caso en el que el código llama a un método público en el objeto una vez que ya se ha eliminado. Llamar a CheckDisposed() al comienzo de un método público no garantiza de ninguna manera que el objeto no se eliminará dentro de ese método. Si considero que es un riesgo inherente en el diseño de mi clase, a diferencia de una condición de error que no pude explicar, entonces uso el método IsDisposed mencionado IsDisposed junto con el bloqueo apropiado.


La mayoría de las implementaciones de BCL de Dispose no son seguras para subprocesos. La idea es que depende del interlocutor de Dispose para asegurarse de que nadie más esté usando la instancia antes de que sea Disposed. En otras palabras, empuja la responsabilidad de sincronización hacia arriba. Esto tiene sentido, ya que de otra manera ahora todos sus otros consumidores necesitan manejar el caso de límite donde el objeto fue Desechado mientras lo estaban usando.

Dicho esto, si desea una clase desechable segura para subprocesos, puede crear un bloqueo alrededor de cada método público (incluido Desechar) con un cheque para _despuesto en la parte superior. Esto puede complicarse más si tiene métodos de larga duración en los que no desea mantener el bloqueo para todo el método.


Lo más simple que puede hacer es marcar la variable privada dispuesta como volatile e inspeccionarla al comienzo de sus métodos. A continuación, puede lanzar una ObjectDisposedException si el objeto ya se ha eliminado.

Hay dos advertencias a esto:

  1. No debe lanzar una ObjectDisposedException si el método es un controlador de eventos. En su lugar, solo debe salir con gracia del método si eso es posible. La razón es que existe una condición de carrera en la que se pueden generar eventos después de que cancele la suscripción. (Vea este artículo de Eric Lippert para más información.)

  2. Esto no evita que su clase sea eliminada mientras usted está en medio de ejecutar uno de sus métodos de clase. Por lo tanto, si su clase tiene miembros de instancia a los que no se puede acceder después de la eliminación, deberá configurar algún comportamiento de bloqueo para garantizar el acceso a estos recursos.

La guía de Microsoft sobre IDisposable dice que debe verificar si se eliminan todos los métodos, pero personalmente no he encontrado esto necesario. La pregunta realmente es si algo va a generar una excepción o causar efectos secundarios no deseados si permite que se ejecute un método después de que se haya eliminado la clase. Si la respuesta es sí, necesita hacer un trabajo para asegurarse de que eso no suceda.

En cuanto a si todas las clases IDisponibles deben ser seguras para subprocesos: No. La mayoría de los casos de uso para clases desechables involucran que solo un solo hilo acceda a ellas.

Dicho esto, es posible que desee investigar por qué necesita que su clase desechable sea segura para subprocesos, ya que agrega mucha complejidad adicional. Puede haber una implementación alternativa que le permita no tener que preocuparse por los problemas de seguridad de subprocesos en su clase desechable.


Prefiero usar enteros e Interlocked.Exchange o Interlocked.CompareExchange en un objeto de tipo entero "dispuesto" o variable "estado"; enum si Interlocked.Exchange o Interlocked.CompareExchange pudieran manejar tales tipos, pero lamentablemente no pueden.

Un punto que la mayoría de las discusiones de IDisposable y finalizadores no mencionan es que mientras que el finalizador de un objeto no debería ejecutarse mientras que IDisposable.Dispose () está en progreso, no hay forma de que una clase evite que los objetos de su tipo se declaren muertos y luego resucitado Para estar seguro, si el código externo permite que eso suceda, obviamente no puede haber ningún requisito de que el objeto "funcione normalmente", pero los métodos de Eliminar y finalizar deberían estar lo suficientemente protegidos para garantizar que no dañen ningún otro objeto. ''estado, que a su vez generalmente requerirá el uso de bloqueos u operaciones Interlocked en variables de estado de objeto.


Tiendo a usar un entero en lugar de un valor booleano como su campo para almacenar el estado dispuesto, porque entonces puede usar la clase Interlocked segura para subprocesos para probar si Dispose ya ha sido llamado.

Algo como esto:

private volatile int _disposeCount; public void Dispose() { if (Interlocked.Increment(ref _disposeCount) == 1) { // disposal code here } }

Esto garantiza que el código de eliminación se llame solo una vez, sin importar cuántas veces se llame al método, y es totalmente seguro para subprocesos.

Luego, cada método puede usar simplemente llamar a este método como un control de barrera:

private void ThrowIfDisposed() { if (_disposeCount > 0) throw new ObjectDisposedException(GetType().Name); }

Con respecto a la sincronización de todos los métodos, ¿está diciendo que una simple barrera de control no funcionará? Que desea detener otros subprocesos que podrían estar ejecutando código en la instancia . Este es un problema más complejo. No sé qué está haciendo su código, pero considere si realmente lo necesita: ¿no funcionará una simple comprobación de barreras?

Si solo quiso decir con respecto al cheque desechado en sí mismo, mi ejemplo anterior está bien.

EDITAR: para responder al comentario "¿Cuál es la diferencia entre esto y un indicador de bool volátil? Es un poco confuso tener un campo llamado somethingCount y permitir que solo contenga valores de 0 y 1"

La volatilidad está relacionada con garantizar que la operación de lectura o escritura sea atómica y segura. No hace que el proceso de asignación y comprobación de un hilo de valor sea seguro. Entonces, por ejemplo, lo siguiente no es seguro para subprocesos a pesar de lo volátil:

private volatile bool _disposed; public void Dispose() { if (!_disposed) { _disposed = true // disposal code here } }

El problema aquí es que si dos subprocesos estuvieran juntos, el primero podría verificar _después, leer en falso, ingresar el bloque de código y desconectarse antes de configurar _dispuesto a verdadero. El segundo luego verifica _despuesto, ve falso y también ingresa el bloque de código.

El uso de Interlocked asegura que tanto la asignación como la lectura posterior son una sola operación atómica.