闭包捕获的变量如何与不同的线程交互?在下面的示例代码中,我想将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语义强加到捕获的局部变量上,所以我将实现一种不同的方式。谢谢你的回答,无论如何我已经学到了很多有用的东西。 :)
答案 0 :(得分:3)
将局部变量标记为volatile是无效的。闭包可以捕获易失性字段,以下是完全合法的:
volatile int totalEvents = 0;
private void WaitFor10Events()
{
_someEventGenerator.SomeEvent += (s, e) => totalEvents++;
...
}
有关volatile关键字的信息,请参阅here for information;
另外,您可以考虑使用重置事件(auto,manual),监控类(pulse和wait方法)或{{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。