为什么局部变量不能在C#中出现波动?

时间:2009-06-23 12:07:26

标签: c# multithreading volatile

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只能是false。特别是当你考虑一个提升变量作为一个类的成员生成的方式,并且委托作为同一个类的方法,从而剥夺了eventFinished曾经是局部变量这一事实的优化。

5 个答案:

答案 0 :(得分: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.0之前没有使用这样的功能。现在,由于存在匿名方法和lambda函数,这种支持可能会变得有用。如果我在这里遗漏了什么,请有人澄清问题。

答案 1 :(得分:10)

大多数方案中,局部变量特定于某个帖子,因此与volatile相关的问题完全没必要。

当像在你的例子中,它是一个“捕获”变量时,当它被静默地实现为编译器生成的类上的字段时,这会发生变化。因此理论上它可以易变,但在大多数情况下,它不值得额外的复杂性。

特别是像Monitor(又名lock)和Pulse之类的东西也可以这样做,就像任何其他线程构造一样。

线程很棘手,主动循环很少是管理它的最佳方式......


重新编辑... 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");
    }
}

答案 2 :(得分:6)

如果你想让局部变量表现为易失性,你也可以使用Voltile.Write。如:

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);
}

答案 3 :(得分:1)

如果在进程退出该局部变量的范围之后直到引发的事件没有完成,会发生什么?该变量将被释放,您的线程将失败。

明智的做法是附加一个委托函数,该函数向父线程指示子线程已经完成。

答案 4 :(得分:0)

我目前正在学习所有这些内容,我自己偶然发现了一个问题:如何可靠地从另一个线程终止正在运行的循环? 我建议的解决方案是将控制变量包装在自定义结构中,以允许使用volatile关键字:

public struct VolatileBool
{
    private volatile bool _value;
    public VolatileBool(bool value) => _value = value;
    public bool Value { get => _value; set => _value = value; }
}

此后,您可以将bool变量的声明更改为 VolatileBool 类型,然后利用该结构的volatile Value 属性。

VolatileBool eventFinished = new VolatileBool(false);
//...
while(!eventFinished.Value) Thread.Sleep(1);

最后是另一个线程:

eventFinished.Value = true;