Making variables captured by a closure volatile

扶醉桌前 提交于 2019-12-05 01:26:46

Volatile.Write to the rescue:

private void WaitFor10Events()
{
     int totalEvents = 0; 

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

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

That said, I would still use Interlocked.Incrementfor this particular case..

It is not valid to have a local variable marked as volatile. A closure can capture volatile fields, the following is perfectly legal:

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

See here for information about the volatile keyword;

As an aside, you might consider using a reset event (auto, manual), the monitor class (pulse and wait methods) or a countdown event to have a thread sleep until an event is raised, it is far more efficient than sleeping in a loop.

Update

Following on from the edit on the question, a simple way to get thread-safe semantics is to use the Interlocked class. To re-write your example in this fashion (although as stated in other answers there are better ways of writing this example):

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

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

You can't declare locals volatile. Besides, there are better ways to acheive your goal... Use System.Threading.CountdownEvent instead. It's going to be more efficient than your poll/sleep method.

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

This won't work if events are fired in parallel. n++, unfortunately, is not an atomic operation in .NET, so you can't just expect multiple threads executing n++ 10 times to actually increase n by 10, it can be increased by less. Here's a little program that proves it (and in the process makes sure closures are properly handled when used in parallel):

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

The Interlocked.Increment variant works as expected.

Local variables captured by closures get "hoisted" out to a different class generated by the compiler, which doesn't loosen the "locals can't be volatile" rule when doing so, even though the local "really" ends up as an instance field. Your best bet is to construct the closure class by hand ("function object") or use some IL manipulation tool, eg. ILDASM.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!