Создание переменных, захваченных замыканием, изменчивым - PullRequest
13 голосов
/ 23 февраля 2012

Как переменные, захваченные замыканием, взаимодействуют с различными потоками?В следующем примере кода я хотел бы объявить totalEvents как volatile, но C # не допускает этого.

(Да, я знаю, что это плохой код, это всего лишь пример)

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

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

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

РЕДАКТИРОВАТЬ : Люди, похоже, немного упускают смысл моего вопроса.Я знаю, что не могу использовать volatile для локальных переменных.Я также знаю, что пример кода кода плох и может быть реализован другими способами, поэтому мой отказ от «плохого кода».Это было просто для иллюстрации проблемы.

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

Ответы [ 5 ]

3 голосов
/ 23 февраля 2012

Недопустимо иметь локальную переменную, помеченную как volatile. Закрытие может захватывать изменчивые поля, совершенно законно следующее:

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

См. здесь для получения информации о ключевом слове volatile;

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

Обновление

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

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

   while(Interlocked.Read(ref totalEvents) < 10)
   {
     Thread.Sleep(100);
   }
}
2 голосов
/ 25 ноября 2015

Летучие. Пишите на помощь:

private void WaitFor10Events()
{
     int totalEvents = 0; 

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

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

Тем не менее, я бы все равно использовал Interlocked.Increment для этого конкретного случая ..

2 голосов
/ 23 февраля 2012

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

2 голосов
/ 23 февраля 2012

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

using(CountdownEvent cde = new CountdownEvent(10))
{
  _someEventGenerator.SomeEvent += (s, e) => cde.Signal();
  cde.Wait();
}
1 голос
/ 23 февраля 2012

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

...