varios thread programacion parametros multitarea multihilo metodos hilos entre ejecutar diferencia con c# .net multithreading

c# - thread - ¿Bloquea correctamente una Lista<T> en Escenarios Multihilo?



programacion multihilo c# (5)

Crear un nuevo bloqueo en DoSomething() sería ciertamente incorrecto, sería inútil, ya que cada llamada a DoSomething() usaría un bloqueo diferente. Deberías usar el segundo formulario, pero con un inicializador:

private static object _lock = new object();

Es cierto que el bloqueo no impide que otra persona acceda a tu lista, pero a menos que expongas la lista directamente, eso no importa: de todos modos, nada más accederá a la lista.

Sí, puede ajustar Start / StopRecording en bloqueos de la misma manera.

Sí, configurar una variable booleana es atómica, pero eso no lo hace seguro para subprocesos. Si solo accedes a la variable dentro del mismo bloqueo, estás bien en términos de atomicidad y volatilidad. De lo contrario, es posible que vea valores "obsoletos"; por ejemplo, establezca el valor en true en un subproceso y otro subproceso podría usar un valor en caché al leerlo.

Está bien, simplemente no puedo entender bien los escenarios de subprocesos múltiples correctamente. Perdón por hacer una pregunta similar nuevamente, solo estoy viendo muchos "hechos" diferentes en Internet.

public static class MyClass { private static List<string> _myList = new List<string>; private static bool _record; public static void StartRecording() { _myList.Clear(); _record = true; } public static IEnumerable<string> StopRecording() { _record = false; // Return a Read-Only copy of the list data var result = new List<string>(_myList).AsReadOnly(); _myList.Clear(); return result; } public static void DoSomething() { if(_record) _myList.Add("Test"); // More, but unrelated actions } }

La idea es que si la grabación está activada, las llamadas a DoSomething () se graban en una lista interna y se devuelven cuando se llama a StopRecording ().

Mi especificación es esta:

  • StartRecording no se considera Thread-Safe. El usuario debe llamar a esto mientras que ningún otro Thread llama a DoSomething (). Pero si de alguna manera podría ser, sería genial.
  • StopRecording tampoco es oficialmente seguro para subprocesos. Una vez más, sería genial si pudiera ser, pero eso no es un requisito.
  • DoSomething tiene que ser seguro para subprocesos

La forma habitual parece ser:

public static void DoSomething() { object _lock = new object(); lock(_lock){ if(_record) _myList.Add("Test"); } // More, but unrelated actions }

Alternativamente, declarar una variable estática:

private static object _lock; public static void DoSomething() { lock(_lock){ if(_record) _myList.Add("Test"); } // More, but unrelated actions }

Sin embargo, esta respuesta dice que esto no impide que otros códigos accedan a él.

Entonces me pregunto

  • ¿Cómo puedo bloquear correctamente una lista?
  • ¿Debo crear el objeto de bloqueo en mi función o como una variable de clase estática?
  • ¿Puedo ajustar la funcionalidad de Iniciar y Detener Grabación en un bloque de bloqueo también?
  • StopRecording () hace dos cosas: establecer una variable booleana en falso (para evitar que DoSomething () agregue más cosas) y luego copiar la lista para devolver una copia de los datos al llamador). Supongo que _record = falso; es atómico y estará en efecto inmediatamente? Entonces, normalmente, no tendría que preocuparme por Multi-Threading aquí, a menos que algún otro Thread llame a StartRecording () nuevamente.

Al final del día, estoy buscando una forma de expresar "Está bien, esta lista es mía ahora, todos los demás hilos tienen que esperar hasta que haya terminado".


Es posible que esté malinterpretando esta respuesta , lo que en realidad se afirma es que la instrucción de lock no bloquea realmente el objeto en cuestión para evitar que se modifique, sino que impide que se ejecute cualquier otro código que utilice ese objeto como fuente de bloqueo.

Lo que realmente significa es que cuando usa la misma instancia que el objeto de bloqueo, el código dentro del bloque de bloqueo no se debe ejecutar.

Básicamente, no está intentando "bloquear" su lista, está intentando tener una instancia común que pueda usarse como punto de referencia para cuando quiera modificar su lista, cuando esté en uso o "bloqueado" desee para evitar que se ejecute otro código que potencialmente modificaría la lista.


Hay algunas formas de bloquear la lista. Puede bloquear en _myList directamente siempre que _myList nunca se cambie para hacer referencia a una nueva lista.

lock (_myList) { // do something with the list... }

Puede crear un objeto de bloqueo específicamente para este propósito.

private static object _syncLock = new object(); lock (_syncLock) { // do something with the list... }

Si la colección estática implementa la interfaz System.Collections.ICollection (List (T) does), también puede sincronizar utilizando la propiedad SyncRoot.

lock (((ICollection)_myList).SyncRoot) { // do something with the list... }

El punto principal que hay que entender es que desea que uno y solo un objeto se use como su bloqueo sentinal, por lo que la creación del bloqueo sentinal dentro de la función DoSomething () no funcionará. Como dijo Jon, cada hilo que llame a DoSomething () obtendrá su propio objeto, por lo que el bloqueo de ese objeto tendrá éxito cada vez y otorgará acceso inmediato a la lista. Al hacer que el objeto de bloqueo sea estático (a través de la propia lista, un objeto de bloqueo dedicado o la propiedad ICollection.SyncRoot), se comparte entre todos los hilos y puede serializar efectivamente el acceso a su lista.


La primera forma es incorrecta , ya que cada persona que llama se bloqueará en un objeto diferente. Podrías simplemente bloquear la lista.

lock(_myList) { _myList.Add(...) }


Me fijaré en _myList aquí porque es privado, pero usar una variable separada es más común. Para mejorar en algunos puntos:

public static class MyClass { private static List<string> _myList = new List<string>; private static bool _record; public static void StartRecording() { lock(_myList) // lock on the list { _myList.Clear(); _record = true; } } public static IEnumerable<string> StopRecording() { lock(_myList) { _record = false; // Return a Read-Only copy of the list data var result = new List<string>(_myList).AsReadOnly(); _myList.Clear(); return result; } } public static void DoSomething() { lock(_myList) { if(_record) _myList.Add("Test"); } // More, but unrelated actions } }

Tenga en cuenta que este código usa lock(_myList) para sincronizar el acceso a _myList y _record. Y necesita sincronizar todas las acciones en esos dos.

Y para estar de acuerdo con las otras respuestas aquí, lock(_myList) no le hace nada a _myList, simplemente usa _myList como un token (presumiblemente como clave en un HashSet). Todos los métodos deben jugar limpio pidiendo permiso usando la misma ficha. Un método en otro hilo todavía puede usar _myList sin bloqueo primero, pero con resultados impredecibles.

Podemos usar cualquier token, por lo que a menudo creamos uno especialmente:

private static object _listLock = new object();

Y luego use lock(_listLock) lugar de lock(_myList) todas partes.

Esta técnica hubiera sido aconsejable si myList hubiera sido pública, y hubiera sido absolutamente necesario si hubiera vuelto a crear myList en lugar de llamar a Clear ().