2009-06-23 3 views
15
public void MyTest() 
{ 
    bool eventFinished = false; 

    myEventRaiser.OnEvent += delegate { doStuff(); eventFinished = true; }; 
    myEventRaiser.RaiseEventInSeperateThread() 

    while(!eventFinished) Thread.Sleep(1); 

    Assert.That(stuff); 
} 

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

Мне кажется, что в этом случае компилятор или среда выполнения могут стать умными для собственного блага и «знать» в цикле while, что eventFinished может быть только ложным. Особенно, когда вы рассматриваете способ получения поднятой переменной как члена класса и делегата как метода того же класса и тем самым лишает оптимизацию того факта, что eventFinished был когда-то локальной переменной.

+2

В данном случае ваша переменная не является локальной! Скорее, это переменная экземпляра в классе, генерируемом компилятором. –

+4

Это семантическая разница - с точки зрения кода, это локальная переменная, которая может быть захвачена ... –

+1

Что я вижу? Это локальная переменная, которая обновляется в другом потоке. И хотя я знаю, что компилятор сделает переменную экземпляра моей локальной переменной, предкомпилятор, по-видимому, не слишком «упрям», чтобы признать это. –

ответ

12

Для выполнения этой задачи существует примитив с потоком, ManualResetEvent - вы не хотите использовать булевский флаг.

Что-то, как это должно сделать работу:

public void MyTest() 
{ 
    var doneEvent = new ManualResetEvent(false); 

    myEventRaiser.OnEvent += delegate { doStuff(); doneEvent.Set(); }; 
    myEventRaiser.RaiseEventInSeparateThread(); 
    doneEvent.WaitOne(); 

    Assert.That(stuff); 
} 

Что касается отсутствия поддержки volatile ключевого слова на локальном переменных, я не верю, что есть какая-либо причина, почему это может не в теории быть возможно в C#. Скорее всего, он не поддерживается просто потому, что для C++ такой возможности не было. Теперь, с существованием анонимных методов и лямбда-функций, такая поддержка потенциально может оказаться полезной. Кто-то, пожалуйста, уточните, если я что-то пропустил.

+2

Где Эрик, когда он вам нужен? ;-p –

+0

Хе-хе. В самом деле, сейчас мы попадаем в темные области C#/CLR, на которых я уверен, что он может пролить свет. – Noldorin

+0

Благодарим за использование ManualResetEvent. Он даже работает, когда событие вызывается в том же потоке, что я не хотел исключать в MyTest(). –

1

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

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

+0

Код в порядке; которая является «захваченной» переменной и реализуется как поле в классе, генерируемом компилятором. Нить не подведет. –

+0

Не мог ли быть тот же случай с переменной уровня класса? Если экземпляр собран из мусора до того, как событие в другом потоке заканчивается, возникает такая же проблема. – Noldorin

+0

Невозможно собрать мусор, пока он все еще находится в видимой области видимости любого потока. –

10

В наиболее сценарии, локальные переменные являются специфическими для потока, поэтому проблемы, связанные с volatile, совершенно не нужны.

Это изменяется, когда, как и в вашем примере, это «захваченная» переменная - когда она тихо реализована как поле в классе, генерируемом компилятором. Так что теоретически мог бы быть изменчивым, но в большинстве случаев это не стоило бы лишней сложности.

В частности, что-то вроде Monitor (aka lock) с Pulse и т. Д. Может сделать это так же хорошо, как и любое количество других конструкций резьбы.

Threading является сложным, и активный цикл редко лучший способ управлять ...


Re редактирования ... 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"); 
    } 
} 
+0

Что делать, если RaiseEventInSeperateThread() был эффективно реализован как: new Thread (() => {Thread.sleep (100); OnEvent();}; как бы вы использовали монитор или блокировку для MyTest() подождите, пока делегат события завершит выполнение? –

+0

Я имею ввиду: новый поток (() => {Thread.sleep (100); OnEvent();}). Start(); –

+0

У вас будет один поток WaitOne и другой импульс. Я попытаюсь добавить пример позже ... –

4

Вы также можете использовать 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); 
} 
+1

хороший ответ, но ваш цикл while должен использовать 'Volatile.Read' – CoderBrien