una traves todos thread subprocesos subproceso seguras requiere realizar que para otro metodo los llamó llamar llamadas interfaz función formularios form evaluación ejecuten diferente desde controles control como checkforillegalcrossthreadcalls aplicación aplanó acceder c# concurrency static-methods

traves - ¿Cómo puedo saber si este método C#es seguro para subprocesos?



realizar llamadas seguras para subprocesos en controles de formularios windows forms (11)

Estoy trabajando en la creación de una función de devolución de llamada para un evento de eliminación de elementos de caché ASP.NET.

La documentación dice que debo llamar a un método sobre un objeto o las llamadas que sé que existirán (estarán dentro del alcance), como un método estático, pero dijo que necesito asegurarme de que la estática es segura para subprocesos.

Parte 1: ¿Cuáles son algunos ejemplos de cosas que podría hacer para que sea seguro?

Parte 2: ¿Esto significa que si tengo

static int addOne(int someNumber){ int foo = someNumber; return foo +1; }

y llamo Class.addOne (5); y Class.addOne (6); Simultáneamente, ¿puedo obtener 6 o 7 devueltos dependiendo de a qué invocación se fija primero? (es decir, una condición de carrera)


En cualquier lugar, la seguridad de subprocesos significa que no tiene dos o más subprocesos colisionando cuando está accediendo a un recurso. Por lo general, las variables estáticas --- en idiomas como C #, VB.NET y Java --- hacen que el código sea inseguro .

En Java existe la palabra clave sincronizada . Pero en .NET obtienes la opción / directiva de ensamblado:

class Foo { [MethodImpl(MethodImplOptions.Synchronized)] public void Bar(object obj) { // do something... } }

Los ejemplos de clases no seguras de subprocesos deben ser simples , dependiendo de cómo se codifica este patrón. Por lo general, debe implementar un creador de instancias sincronizado .

Si no quiere un método sincronizado , puede probar el método de bloqueo, como el bloqueo de giro .


En el ejemplo anterior, no.

La seguridad del subproceso es principalmente para hacer con el estado almacenado. Puede hacer que el ejemplo anterior no sea seguro para hilos al hacer esto:

static int myInt; static int addOne(int someNumber){ myInt = someNumber; return myInt +1; }

Esto significa que debido al cambio de contexto, el hilo 1 podría llegar a la llamada myInt = someNumber y luego al cambio de contexto, digamos thread 1, simplemente configúrelo en 5. Entonces imagine que el hilo 2 entra y usa 6 y devuelve 7. Then when thread 1 se despierta nuevamente, tendrá 6 en myInt en lugar de los 5 que estaba usando y devolverá 7 en lugar de los 6. esperados: O


Esa función addOne es, de hecho, segura para subprocesos porque no accede a ningún dato al que pueda accederse otro subproceso. Las variables locales no se pueden compartir entre subprocesos porque cada subproceso tiene su propia pila. Sin embargo, debe asegurarse de que los parámetros de la función sean tipos de valores y no tipos de referencia.

static void MyFunction(int x) { ... } // thread safe. The int is copied onto the local stack. static void MyFunction(Object o) { ... } // Not thread safe. Since o is a reference type, it might be shared among multiple threads.


Hay algunas investigaciones que le permiten detectar código que no es seguro para subprocesos. Por ejemplo, el proyecto CHESS en Microsoft Research .


La razón por la que ''foo'' y ''someNumber'' son seguros en su ejemplo es que residen en la pila, y cada subproceso tiene su propia pila y, por lo tanto, no se comparten.

Tan pronto como los datos tienen el potencial para ser compartidos, por ejemplo, ser globales o compartir punteros a los objetos, entonces podría tener conflictos y es posible que necesite usar bloqueos de algún tipo.


No, addOne es seguro para subprocesos aquí; solo usa variables locales. Aquí hay un ejemplo que no sería seguro para subprocesos:

class BadCounter { private static int counter; public static int Increment() { int temp = counter; temp++; counter = temp; return counter; } }

Aquí, dos hilos podrían llamar Incremento al mismo tiempo, y terminar incrementando solo una vez. (Por cierto, el uso del return ++counter; sería igual de malo, lo anterior es una versión más explícita de lo mismo. Lo expandí, por lo que sería más obviamente incorrecto).

Los detalles de lo que es y no es seguro para subprocesos pueden ser bastante complicados, pero en general si no estás mutando ningún estado (aparte de lo que te pasó, de todos modos, un poco de un área gris allí) entonces generalmente está bien .


Su método está bien ya que solo usa variables locales, cambiemos su método un poco:

static int foo; static int addOne(int someNumber) { foo=someNumber; return foo++; }

Este no es un método seguro para subprocesos porque estamos tocando datos estáticos. Esto necesitaría ser modificado para ser:

static int foo; static object addOneLocker=new object(); static int addOne(int someNumber) { int myCalc; lock(addOneLocker) { foo=someNumber; myCalc= foo++; } return myCalc; }

Lo cual creo que es una muestra tonta que acabo de causar porque si lo estoy leyendo correctamente no tiene sentido seguir buscando pero, oye, es una muestra.


cualquier acceso a un objeto que pueda ser usado simultáneamente por dos hilos no es inseguro.

su ejemplo en la Parte 2 es claramente seguro, ya que utiliza solo los valores pasados ​​como argumentos, pero si utilizó una variable de ámbito de objeto, es posible que tenga que rodear el acceso con instrucciones de bloqueo apropiadas


foo no se comparte entre invocaciones concurrentes o secuenciales, por lo que addOne es seguro para subprocesos.


Temas de enhebrado (que también me he estado preocupando últimamente) surgen del uso de múltiples núcleos de procesador con cachés separados, así como de condiciones básicas de cambio de hilo. Si los cachés para núcleos separados acceden a la misma ubicación de memoria, generalmente no tendrán idea acerca del otro y pueden rastrear por separado el estado de esa ubicación de datos sin que vuelva a la memoria principal (o incluso a un caché sincronizado compartido en todos núcleos en L2 o L3, por ejemplo), por razones de rendimiento del procesador. De modo que incluso los trucos de enclavamiento de orden de ejecución pueden no ser confiables en entornos de subprocesos múltiples.

Como puede saber, la herramienta principal para corregir esto es un bloqueo, que proporciona un mecanismo de acceso exclusivo (entre contenciones para el mismo bloqueo) y maneja la sincronización de caché subyacente para acceder a la misma ubicación de memoria mediante varios bloqueos protegidos las secciones de código serán serializadas apropiadamente. Todavía puede haber condiciones de carrera entre quién obtiene el bloqueo cuándo y en qué orden, pero eso suele ser mucho más fácil de tratar cuando puede garantizar que la ejecución de una sección bloqueada sea atómica (dentro del contexto de ese bloqueo).

Puede obtener un bloqueo en una instancia de cualquier tipo de referencia (por ejemplo, hereda de Object, no tipos de valores como int o enums, y no nulo), pero es muy importante comprender que el bloqueo de un objeto no tiene un efecto inherente en los accesos para ese objeto, solo interactúa con otros intentos para obtener el bloqueo en el mismo objeto. Depende de la clase proteger el acceso a sus variables miembro mediante un esquema de bloqueo apropiado. A veces las instancias pueden proteger los accesos de subprocesos múltiples a sus propios miembros al bloquearse (por ejemplo, lock (this) { ... } ), pero generalmente esto no es necesario porque las instancias tienden a estar en manos de un solo propietario y no necesidad de garantizar el acceso a la instancia sin hilos.

Más comúnmente, una clase crea un bloqueo privado (por ejemplo, private readonly object m_Lock = new Object(); para bloqueos separados dentro de cada instancia para proteger el acceso a los miembros de esa instancia, o private static readonly object s_Lock = new Object(); un bloqueo central para proteger el acceso a los miembros estáticos de la clase). Josh tiene un ejemplo de código más específico de usar un candado. Luego debe codificar la clase para usar la cerradura de manera apropiada. En casos más complejos, es posible que desee crear bloqueos separados para diferentes grupos de miembros, para reducir la contención de diferentes tipos de recursos que no se utilizan juntos.

Por lo tanto, para volver a su pregunta original, un método que solo acceda a sus propias variables y parámetros locales sería seguro para subprocesos, ya que existen en sus propias ubicaciones de memoria en la pila específica del subproceso actual, y no se puede acceder en otro lugar, a menos que hayas compartido esas instancias de parámetros en los hilos antes de pasarlos.

Un método no estático que solo acceda a los miembros propios de las instancias (sin miembros estáticos) y, por supuesto, los parámetros y las variables locales, no necesitaría usar bloqueos en el contexto de esa instancia utilizada por un único propietario (no debe ser seguro para subprocesos), pero si las instancias estaban destinadas a ser compartidas y deseaban garantizar el acceso seguro de subprocesos, entonces la instancia necesitaría proteger el acceso a sus variables miembro con uno o más bloqueos específicos para esa instancia (bloqueo en el la propia instancia es una opción), en lugar de dejar que la persona que realiza la llamada implemente sus propios bloqueos cuando comparte algo no destinado a ser compartible con subprocesos.

El acceso a miembros de solo lectura (estáticos o no estáticos) que nunca se manipulan es generalmente seguro, pero si la instancia que contiene no es en sí misma segura para subprocesos o si necesita garantizar la atomicidad a través de múltiples manipulaciones de la misma, entonces puede necesitar para proteger todo acceso a él con su propio esquema de bloqueo también. Ese es un caso en el que podría ser útil si la instancia utiliza el bloqueo sobre sí misma, porque simplemente podría obtener un bloqueo en la instancia a través de múltiples accesos en ella para la atomicidad, pero no necesitaría hacerlo para accesos únicos si es usando un candado en sí mismo para hacer esos accesos individualmente seguros para hilos. (Si no es su clase, debe saber si se bloquea o está usando un bloqueo privado al que no puede acceder externamente).

Y finalmente, hay acceso a miembros estáticos cambiantes (cambiados por el método dado o por cualquier otro) desde una instancia y, por supuesto, métodos estáticos que acceden a esos miembros estáticos y pueden ser llamados de cualquier persona, en cualquier lugar, en cualquier momento, que tienen la mayor necesidad de utilizar un bloqueo responsable, sin el cual definitivamente no es seguro para subprocesos y es probable que cause errores impredecibles.

Al tratar con clases de framework .NET, Microsoft documenta en MSDN si una llamada API dada es segura para subprocesos (por ejemplo, los métodos estáticos de los tipos de colecciones genéricos proporcionados como List<T> se hacen seguros para subprocesos mientras que los métodos de instancia pueden no ser-- pero verifique específicamente para estar seguro). La gran mayoría de las veces (y a menos que específicamente indique que es seguro para subprocesos), no es internamente seguro para subprocesos, por lo que es su responsabilidad usarlo de manera segura. E incluso cuando las operaciones individuales se implementan internamente a prueba de subprocesos, aún debe preocuparse por el acceso compartido y superpuesto de su código si hace algo más complejo que debe ser atómico.

Una gran advertencia es iterar sobre una colección (por ejemplo, con foreach ). Incluso si cada acceso a la colección obtiene un estado estable, no existe una garantía inherente de que no cambiará entre esos accesos (si cualquier otro lugar puede acceder a él). Cuando la colección se realiza localmente, generalmente no hay problema, pero una colección que podría cambiarse (por otro hilo o durante la ejecución de su ciclo) podría producir resultados inconsistentes. Una manera fácil de resolver esto es usar una operación atómica de seguridad de subprocesos (dentro de su esquema de bloqueo de protección) para hacer una copia temporal de la colección ( MyType[] mySnapshot = myCollection.ToArray(); ) y luego iterar sobre esa instantánea local copiar fuera de la cerradura. En muchos casos, esto evita la necesidad de mantener un bloqueo todo el tiempo, pero dependiendo de lo que esté haciendo dentro de la iteración, esto puede no ser suficiente y solo debe protegerse contra los cambios todo el tiempo (o puede que ya lo tenga dentro) una sección bloqueada que protege contra el acceso para cambiar la colección junto con otras cosas, por lo que está cubierto).

Por lo tanto, hay un poco de arte para el diseño seguro de subprocesos, y saber exactamente dónde y cómo obtener bloqueos para proteger las cosas depende en gran medida del diseño general y el uso de su (s) clase (s). Puede ser fácil volverse paranoico y pensar que tienes que conseguir bloqueos para todo, pero realmente se trata de encontrar la capa adecuada para proteger las cosas.


Esto solo sería una condición de carrera si estuviera modificando alguna variable externa a la función. Tu ejemplo no está haciendo eso.

Eso es básicamente lo que estás buscando. El hilo seguro significa que la función:

  1. No modifica los datos externos, o
  2. El acceso a los datos externos está sincronizado correctamente para que solo una función pueda acceder a él en cualquier momento.

Los datos externos pueden ser algo almacenado (base de datos / archivo), o algo interno a la aplicación (una variable, una instancia de una clase, etc.): básicamente cualquier cosa que se declare en cualquier parte del mundo que esté fuera del alcance de la función.

Un ejemplo trivial de una versión segura para su subproceso sería esta:

private int myVar = 0; private void addOne(int someNumber) { myVar += someNumber; }

Si llama esto desde dos subprocesos diferentes sin sincronización, consultar el valor de myVar será diferente dependiendo de si la consulta ocurre después de que todas las llamadas a addOne se completen, o la consulta ocurra entre las dos llamadas, o la consulta ocurra antes de cualquiera de las llamadas.