Почему локальная переменная не может быть изменчивой в С#?

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

Что произойдет, если возникшее событие не завершится до тех пор, пока процесс не выйдет из этой локальной переменной? Переменная будет выпущена, и ваш поток завершится с ошибкой.

Разумным подходом является присоединение функции делегата, которая указывает на родительский поток, который завершен подпотоком.