使闭包捕获的变量变为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 。 我也知道示例代码很糟糕,可能以其他方式实现,因此我的“坏代码”免责声明。 这只是为了说明问题。

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

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

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

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

有关volatile关键字的信息,请参见此处 ;

顺便说一下,您可以考虑使用重置事件( 自动 , 手动 ),监视器类( 脉冲和等待方法)或倒计时事件让线程hibernate直到事件被提升,它比睡在一个事件更有效率环。

更新

从对问题的编辑开始,获得线程安全语义的一种简单方法是使用Interlocked类 。 以这种方式重写您的示例(尽管如其他答案中所述,有更好的方法来编写此示例):

 private void WaitFor10Events() { long totalEvents = 0; _someEventGenerator.SomeEvent += (s, e) => Interlocked.Increment(ref totalEvents); while(Interlocked.Read(ref totalEvents) < 10) { Thread.Sleep(100); } } 

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

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

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

 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 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变体按预期工作。

闭包捕获的局部变量被“提升”到编译器生成的不同类中,这样做时不会放松“本地不能是易失性”规则,即使本地“真的”最终作为实例领域。 最好的办法是手动构造闭包类(“函数对象”)或使用一些IL操作工具,例如。 ILDASM。