Supongamos que tengo una variable miembro en una clase (con tipo de datos de lectura/escritura atómica):
bool m_Done = false;Y luego creo una tarea para establecerlo en verdadero:
Task.Run(() => m_Done = true); No me importa cuándo exactamente m_Done se establecerá en verdadero. Mi pregunta es: ¿tengo una garantía por parte de la especificación del lenguaje C# y la biblioteca paralela de Tareas de que eventualmente m_Done será verdadero si accedo a él desde un subproceso diferente?
Ejemplo:
if(m_Done) { // Do something }Sé que el uso de bloqueos introducirá las barreras de memoria necesarias y m_Done será visible como verdadero más adelante. También puedo usar Volatile.Write al configurar la variable y Volatile.Read al leerla. Veo mucho código escrito de esta manera (sin bloqueos o volátil) y no estoy seguro de si es correcto.
Tenga en cuenta que mi pregunta no está dirigida a una implementación específica de C# o .Net, sino a la especificación. Necesito saber si el código actual se comportará de manera similar si se ejecuta en x86, x64, Itanium o ARM.
No me importa cuándo exactamente m_Done se establecerá en verdadero. Mi pregunta es: ¿tengo una garantía por parte de la especificación del lenguaje C# y la biblioteca paralela de Tareas de que eventualmente m_Done será verdadero si accedo a él desde un subproceso diferente?
No.
La lectura de m_Done no es volátil y, por lo tanto, puede retroceder arbitrariamente en el tiempo y el resultado puede almacenarse en caché. Como resultado, se puede observar que es false en cada lectura durante todo el tiempo.
Necesito saber si el código actual se comportará de manera similar si se ejecuta en x86, x64, Itanium o ARM.
La especificación no garantiza que se observará que el código hace lo mismo en modelos de memoria fuertes (x86) y débiles (ARM).
La especificación es bastante clara sobre las garantías que se ofrecen sobre las lecturas y escrituras no volátiles: que pueden reordenarse arbitrariamente en diferentes subprocesos en ausencia de ciertos eventos especiales, como bloqueos.
Lea las especificaciones para conocer los detalles, particularmente la parte sobre los efectos secundarios relacionados con el acceso volátil. Si tiene más preguntas después de eso, publique una nueva pregunta. Esto es algo muy complicado.
Además, la pregunta presupone que está ignorando los mecanismos existentes que determinan que una tarea se complete y, en cambio, está haciendo los suyos propios. Los mecanismos existentes fueron diseñados por expertos; usalos, usalos a ellos.
Veo mucho código escrito de esta manera (sin bloqueos o volátil) y no estoy seguro de si es correcto.
Es casi seguro que no lo es.
Un buen ejercicio para plantearle a la persona que escribió ese código es este:
static volatile bool q = false; static volatile bool r = false; static volatile bool s = false; static volatile bool t = false; static object locker = new object(); static bool GetR() { return r; } // No lock! static void SetR() { lock(locker) { r = true; } } static void MethodOne() { q = true; if (!GetR()) s = true; } static void MethodTwo() { SetR(); if (!q) t = true; } Después de la inicialización de los campos, MethodOne se llama desde un subproceso, MethodTwo se llama desde otro. Tenga en cuenta que todo es volátil y que la escritura en r no solo es volátil, sino que está completamente delimitada. Ambos métodos se completan normalmente. ¿Es posible después que s y t se observen como verdaderos en el primer hilo? ¿Es posible en x86? Parece que no; si el primer hilo gana la carrera entonces t sigue siendo falso, y si el segundo hilo gana entonces s sigue siendo falso; este análisis es incorrecto. ¿Por qué? (Pista: ¿cómo se permite que x86 reescriba MethodOne ?)
Si el codificador no puede responder a esta pregunta, es casi seguro que no podrá programar correctamente con volatile y no debería compartir memoria entre subprocesos sin bloqueos.
pruebe este código, cree la versión, ejecútelo sin Visual Studio:
class Foo { private bool m_Done = false; public void A() { Task.Run(() => { m_Done = true; }); } public void B() { for (; ; ) { if (m_Done) break; } Console.WriteLine("finished..."); } } class Program { static void Main(string[] args) { var o = new Foo(); oA(); oB(); Console.ReadKey(); } }tienes buenas posibilidades de verlo funcionando para siempre