Создание переменных, захваченных закрытием 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 на локальных vars. Я также знаю, что пример кода кода плох и может быть реализован другими способами, отсюда мой отказ от "плохого кода". Просто чтобы проиллюстрировать проблему.

в любом случае, похоже, что нет способа заставить летучую семантику на захваченные локальные переменные, поэтому я буду реализовывать другой способ. Спасибо за ответы, я все равно узнал пару полезных вещей. :)

5 ответов


летучие.Напишите на помощь:

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;

кроме того, рекомендуется использовать сброс события (авто, руководство), класс Monitor (пульс и ждать методы) или событие чтобы иметь поток сна до тех пор, пока событие не будет поднято, оно намного эффективнее, чем спать в цикле.

обновление

после редактирования вопроса простой способ получить потокобезопасную семантику-использовать сблокированного класс. Чтобы переписать свой пример таким образом (хотя, как указано в других ответах, есть лучшие способы написания этого примера):

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

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

вы не можете объявить местных жителей volatile. Кроме того, есть лучшие способы достичь своей цели... Использовать System.Threading.CountdownEvent вместо. Это будет более эффективно, чем ваш метод опроса/сна.

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

Это не будет работать, если события запускаются параллельно. 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();
    }
}

Сблокированного.Вариант инкремента работает как ожидалось.


локальные переменные, захваченные замыканиями get "подсадил" в другой класс, сгенерированный компилятором, который не ослабляет правило "locals не может быть изменчивым" при этом, даже если локальное "действительно" заканчивается как поле экземпляра. Лучше всего построить класс закрытия вручную ("объект функции") или использовать какой-либо инструмент манипуляции IL, например. ILDASM.