使闭包捕获的变量变为volatile

时间:2012-02-23 12:29:24

标签: c# closures volatile

闭包捕获的变量如何与不同的线程交互?在下面的示例代码中,我想将totalEvents声明为volatile,但C#不允许这样做。

(是的,我知道这是错误的代码,它只是一个例子)

private void WaitFor10Events()
{
     volatile int totalEvents = 0; // error CS0106:

     _someEventGenerator.SomeEvent += (s, e) => totalEvents++;

     while(totalEvents < 10)
        Thread.Sleep(100);
}

编辑:人们似乎忽略了我的问题。我知道我无法在本地变量上使用volatile。我也知道示例代码代码很糟糕,可能以其他方式实现,因此我的错误代码&#34;免责声明。这只是为了说明问题。

无论如何,似乎没有办法强制将volatile语义强加到捕获的局部变量上,所以我将实现一种不同的方式。谢谢你的回答,无论如何我已经学到了很多有用的东西。 :)

5 个答案:

答案 0 :(得分:3)

将局部变量标记为volatile是无效的。闭包可以捕获易失性字段,以下是完全合法的:

volatile int totalEvents = 0;
private void WaitFor10Events()
{
   _someEventGenerator.SomeEvent += (s, e) => totalEvents++;
   ...
}

有关volatile关键字的信息,请参阅here for information;

另外,您可以考虑使用重置事件(automanual),监控类(pulsewait方法)或{{3让一个线程休眠直到事件被引发,它比在一个循环中休眠更有效。

<强>更新

继续编辑问题之后,获取线程安全语义的一种简单方法是使用countdown event。以这种方式重写你的例子(尽管如其他答案中所述,有更好的方法来编写这个例子):

private void WaitFor10Events()
{
   long totalEvents = 0;
   _someEventGenerator.SomeEvent += (s, e) => Interlocked.Increment(ref totalEvents);

   while(Interlocked.Read(ref totalEvents) < 10)
   {
     Thread.Sleep(100);
   }
}

答案 1 :(得分:2)

您无法声明本地volatile。此外,有更好的方法来实现您的目标......请改用System.Threading.CountdownEvent。它比你的民意调查/睡眠方法更有效率。

using(CountdownEvent cde = new CountdownEvent(10))
{
  _someEventGenerator.SomeEvent += (s, e) => cde.Signal();
  cde.Wait();
}

答案 2 :(得分:2)

如果并行触发事件,则无效。遗憾的是,n ++不是.NET中的原子操作,所以你不能期望多个线程执行n ++ 10次以实际增加n 10,它可以增加更少。这是一个证明它的小程序(并且在此过程中确保在并行使用时正确处理闭包):

class Program
{
    static volatile int _outer = 0;
    static void Main(string[] args)
    {
        int inner = 0;

        Action act_outer1 = () => _outer++; // Interlocked.Increment(ref _outer);
        Action act_inner1 = () => inner++;  // Interlocked.Increment(ref inner);
        Action<int> combined = (i) => { act_outer1(); act_inner1(); };

        Console.WriteLine("None outer={0}, inner={1}", _outer, inner);
        Parallel.For(0, 20000000, combined);
        Console.WriteLine("Once outer={0}, inner={1}", _outer, inner);
        Console.ReadKey();
    }
}

Interlocked.Increment变体按预期工作。

答案 3 :(得分:2)

Volatile.Write救援:

private void WaitFor10Events()
{
     int totalEvents = 0; 

     _someEventGenerator.SomeEvent += (s, e) => Volatile.Write(ref totalEvents, totalEvents+1);

     while(totalEvents < 10)
        Thread.Sleep(100);
}

那就是说,对于这个特殊情况,我仍然会使用Interlocked.Increment ..

答案 4 :(得分:1)

闭包捕获的局部变量将"hoisted"输出到编译器生成的不同类,这样做时不会放松“本地不能是易失性”规则,即使本地“真的”最终作为实例字段。您最好的选择是手动构造闭包类(“函数对象”)或使用一些IL操作工具,例如。 ILDASM。