Почему локальная переменная не может быть изменчивой в С#?
public void MyTest()
{
bool eventFinished = false;
myEventRaiser.OnEvent += delegate { doStuff(); eventFinished = true; };
myEventRaiser.RaiseEventInSeperateThread()
while(!eventFinished) Thread.Sleep(1);
Assert.That(stuff);
}
Почему eventFinished не может быть изменчивым и имеет значение?
Мне кажется, что в этом случае компилятор или среда выполнения могут стать умными для собственного блага и "знать" в цикле while, что eventFinished может быть только ложным. Особенно, когда вы рассматриваете способ получения поднятой переменной как члена класса и делегата как метода того же класса и тем самым лишает оптимизацию того факта, что eventFinished когда-то была локальной переменной.
Ответы
Ответ 1
Для выполнения этой задачи существует примитив с потоком, ManualResetEvent
- вы не хотите использовать логический флаг.
Что-то вроде этого должно выполнить эту задачу:
public void MyTest()
{
var doneEvent = new ManualResetEvent(false);
myEventRaiser.OnEvent += delegate { doStuff(); doneEvent.Set(); };
myEventRaiser.RaiseEventInSeparateThread();
doneEvent.WaitOne();
Assert.That(stuff);
}
Относительно отсутствия поддержки ключевого слова volatile
для локальных переменных, я не верю, что есть какая-то причина, почему это теоретически невозможно в теории С#. Скорее всего, он не поддерживается просто потому, что для С++ такой возможности не было. Теперь, с существованием анонимных методов и лямбда-функций, такая поддержка потенциально может оказаться полезной. Кто-то, пожалуйста, проясните вопросы, если я что-то упустил.
Ответ 2
В сценариях наиболее локальные переменные специфичны для потока, поэтому проблемы, связанные с volatile
, совершенно не нужны.
Это изменяется, когда, как и в вашем примере, это "захваченная" переменная - когда она тихо внедряется как поле в классе, генерируемом компилятором. Поэтому теоретически это может быть неустойчивым, но в большинстве случаев это не стоило бы лишней сложности.
В частности, что-то вроде Monitor
(aka lock
) с Pulse
и т.д. могло бы сделать это так же хорошо, как и любое количество других конструкций потоков.
Threading сложна, и активный цикл редко является лучшим способом управления им...
Re edit... secondThread.Join()
будет очевидным, но если вы действительно хотите использовать отдельный токен, см. ниже. Преимущество этого (над такими вещами, как ManualResetEvent
) заключается в том, что он ничего не требует от ОС - он обрабатывается исключительно внутри 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
Вы также можете использовать Voltile.Write, если хотите, чтобы локальный var работал как volatile. Как в:
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
Что произойдет, если возникшее событие не завершится до тех пор, пока процесс не выйдет из этой локальной переменной? Переменная будет выпущена, и ваш поток завершится с ошибкой.
Разумным подходом является присоединение функции делегата, которая указывает на родительский поток, который завершен подпотоком.