为什么 C# 中的局部变量不能是 volatile?

发布于 2024-07-25 01:22:53 字数 466 浏览 11 评论 0原文

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 曾经是局部变量这一事实的优化。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

独﹏钓一江月 2024-08-01 01:22:54

如果你想让本地变量表现得像 Volatile,你也可以使用 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);
}

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);
}
猥︴琐丶欲为 2024-08-01 01:22:54

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

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

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.

南七夏 2024-08-01 01:22:53

存在一个线程原语 ManualResetEvent 要精确地完成此任务 - 您不想使用布尔标志。

像这样的事情应该可以完成这项工作:

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

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

    Assert.That(stuff);
}

关于局部变量上缺乏对 volatile 关键字的支持,我不认为有任何理由可以解释为什么这在理论上是不可能的 em> 在 C# 中。 最有可能的是,它不受支持只是因为在 C# 2.0 之前没有使用此功能。 现在,随着匿名方法和 lambda 函数的存在,这种支持可能会变得有用。 如果我在这里遗漏了什么,请有人澄清。

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.

小巷里的女流氓 2024-08-01 01:22:53

大多数场景中,局部变量是特定于线程的,因此与易失性相关的问题是完全没有必要的。

当它是一个“捕获的”变量时(就像在您的示例中一样),当它被默默地实现为编译器生成的类上的字段时,这种情况会发生变化。 因此,从理论上讲,它可能会不稳定,但在大多数情况下,不值得付出额外的复杂性。

特别是,带有 Pulse 等的 Monitor(又名 lock)等也可以做到这一点,任何数量的其他线程构造也可以做到这一点。

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


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

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");
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文