statics - virtual c#
¿Por qué una variable local no puede ser volátil en C#? (5)
¿Qué pasaría si el evento provocado no se completara hasta que el proceso hubiera salido del alcance de esa variable local? La variable habría sido lanzada y tu hilo fallaría.
El enfoque sensato es adjuntar una función de delegado que indique al subproceso principal que el subproceso ha finalizado.
public void MyTest()
{
bool eventFinished = false;
myEventRaiser.OnEvent += delegate { doStuff(); eventFinished = true; };
myEventRaiser.RaiseEventInSeperateThread()
while(!eventFinished) Thread.Sleep(1);
Assert.That(stuff);
}
¿Por qué el evento finalizado no puede ser volátil y importa?
Me parece que, en este caso, el compilador o el tiempo de ejecución podrían volverse muy inteligentes por su propio bien y "saber" en el bucle while que eventFinished solo puede ser falso. Especialmente cuando se considera la forma en que se genera una variable elevada como miembro de una clase y el delegado como método de esa misma clase y, por lo tanto, se privan las optimizaciones del hecho de que eventFinished fue una vez una variable local.
Actualmente estoy aprendiendo sobre todas estas cosas, y me encontré con el mismo problema: ¿cómo terminar un ciclo de ejecución de forma confiable desde otro hilo? La solución que propongo es envolver la variable de control dentro de una estructura personalizada, para permitir el uso de la palabra clave volátil:
public struct VolatileBool
{
private volatile bool _value;
public VolatileBool(bool value) => _value = value;
public bool Value { get => _value; set => _value = value; }
}
Después de eso, podría cambiar la declaración de su variable bool para que sea del tipo VolatileBool y luego hacer uso de la propiedad de valor volátil de la estructura.
VolatileBool eventFinished = new VolatileBool(false);
//...
while(!eventFinished.Value) Thread.Sleep(1);
Y finalmente de otro hilo:
eventFinished.Value = true;
En la mayoría de los escenarios, las variables locales son específicas de un hilo, por lo que los problemas asociados con la volatile
son completamente innecesarios.
Esto cambia cuando, como en su ejemplo, es una variable "capturada", cuando se implementa silenciosamente como un campo en una clase generada por compilador. Entonces, en teoría, podría ser volátil, pero en la mayoría de los casos no valdría la pena la complejidad adicional.
En particular, algo como un Monitor
(también conocido como lock
) con Pulse
etc. podría hacerlo también, al igual que cualquier otra cantidad de construcciones de subprocesos.
El enhebrado es complicado, y un bucle activo rara vez es la mejor manera de administrarlo ...
Re la edición ... secondThread.Join()
sería lo más obvio, pero si realmente quiere usar un token separado, vea más abajo. La ventaja de esto (sobre cosas como ManualResetEvent
) es que no requiere nada del sistema operativo, se maneja únicamente dentro de la CLI.
using System;
using System.Threading;
static class Program {
static void WriteLine(string message) {
Console.WriteLine(Thread.CurrentThread.Name + ": " + message);
}
static void Main() {
Thread.CurrentThread.Name = "Main";
object syncLock = new object();
Thread thread = new Thread(DoStuff);
thread.Name = "DoStuff";
lock (syncLock) {
WriteLine("starting second thread");
thread.Start(syncLock);
Monitor.Wait(syncLock);
}
WriteLine("exiting");
}
static void DoStuff(object lockHandle) {
WriteLine("entered");
for (int i = 0; i < 10; i++) {
Thread.Sleep(500);
WriteLine("working...");
}
lock (lockHandle) {
Monitor.Pulse(lockHandle);
}
WriteLine("exiting");
}
}
Existe una primitiva de subprocesamiento, ManualResetEvent
para realizar precisamente esta tarea: no desea utilizar una bandera booleana.
Algo como esto debería hacer el trabajo:
public void MyTest()
{
var doneEvent = new ManualResetEvent(false);
myEventRaiser.OnEvent += delegate { doStuff(); doneEvent.Set(); };
myEventRaiser.RaiseEventInSeparateThread();
doneEvent.WaitOne();
Assert.That(stuff);
}
Con respecto a la falta de soporte para la palabra clave volatile
en las variables locales, no creo que haya ninguna razón por la que esto no sea posible en teoría en C #. Lo más probable es que no sea compatible simplemente porque no se usó esa función antes de C # 2.0. Ahora, con la existencia de métodos anónimos y funciones lambda, ese soporte podría potencialmente ser útil. Alguien por favor aclara las cosas si me falta algo aquí.
También puede usar Voltile.Write si desea que la var local se comporte como Volatile. Como en:
public void MyTest()
{
bool eventFinished = false;
myEventRaiser.OnEvent += delegate { doStuff(); Volatile.Write(ref eventFinished, true); };
myEventRaiser.RaiseEventInSeperateThread()
while(!Volatile.Read(eventFinished)) Thread.Sleep(1);
Assert.That(stuff);
}