问题
public void MyTest()
{
bool eventFinished = false;
myEventRaiser.OnEvent += delegate { doStuff(); eventFinished = true; };
myEventRaiser.RaiseEventInSeperateThread()
while(!eventFinished) Thread.Sleep(1);
Assert.That(stuff);
}
Why can't eventFinished be volatile and does it matter?
It would seem to me that in this case the compiler or runtime could become to smart for its own good and 'know' in the while loop that eventFinished can only be false. Especially when you consider the way a lifted variable gets generated as a member of a class and the delegate as a method of that same class and thereby depriving optimizations of the fact that eventFinished was once a local variable.
回答1:
There exists a threading primitive, ManualResetEvent to do precisely this task - you don't want to be using a boolean flag.
Something like this should do the job:
public void MyTest()
{
var doneEvent = new ManualResetEvent(false);
myEventRaiser.OnEvent += delegate { doStuff(); doneEvent.Set(); };
myEventRaiser.RaiseEventInSeparateThread();
doneEvent.WaitOne();
Assert.That(stuff);
}
Regarding the lack of support for the volatile keyword on local variables, I don't believe there is any reason why this might not in theory be possible in C#. Most likely, it is not supported simply because there was no use for such a feature prior to C# 2.0. Now, with the existence of anonymous methods and lambda functions, such support could potentially become useful. Someone please clarify matters if I'm missing something here.
回答2:
In most scenarios, local variables are specific to a thread, so the issues associated with volatile are completely unnecessary.
This changes when, like in your example, it is a "captured" variable - when it is silently implemented as a field on a compiler-generated class. So in theory it could be volatile, but in most cases it wouldn't be worth the extra complexity.
In particular, something like a Monitor (aka lock) with Pulse etc could do this just as well, as could any number of other threading constructs.
Threading is tricky, and an active loop is rarely the best way to manage it...
Re the edit... secondThread.Join() would be the obvious thing - but if you really want to use a separate token, see below. The advantage of this (over things like ManualResetEvent) is that it doesn't require anything from the OS - it is handled purely inside the 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");
}
}
回答3:
You could also use Voltile.Write if you want to make the local var behave as Volatile. As in:
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);
}
回答4:
What would happen if the Event raised didn't complete until after the process had exited the scope of that local variable? The variable would have been released and your thread would fail.
The sensible approach is to attach a delegate function that indicates to the parent thread that the sub-thread has completed.
回答5:
I am currently learning about all this stuff, and I stumbled upon the same issue myself: how to terminate a running loop reliably from another thread? The solution I propose is to wrap the control variable inside a custom struct, to allow the use of the volatile keyword:
public struct VolatileBool
{
private volatile bool _value;
public VolatileBool(bool value) => _value = value;
public bool Value { get => _value; set => _value = value; }
}
After that you could change the declaration of your bool variable to be of type VolatileBool, and then make use of the volatile Value property of the struct.
VolatileBool eventFinished = new VolatileBool(false);
//...
while(!eventFinished.Value) Thread.Sleep(1);
And finally from another thread:
eventFinished.Value = true;
来源:https://stackoverflow.com/questions/1032154/why-cant-a-local-variable-be-volatile-in-c