Насколько BackgroundWorker.CancellationPending является потокобезопасным? - PullRequest
22 голосов
/ 06 августа 2011

Чтобы отменить операцию BackgroundWorker, нужно вызвать BackgroundWorker.CancelAsync():

// RUNNING IN UI THREAD
private void cancelButton_Click(object sender, EventArgs e)
{
    backgroundWorker.CancelAsync();
}

В обработчике событий BackgroundWorker.DoWork мы проверяем BackgroundWorker.CancellationPending:

// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    while (!backgroundWorker.CancellationPending) {
        DoSomething();
    }
}

Вышеприведенная идея распространяется по всему Интернету, включая страницу MSDN для BackgroundWorker .

Теперь мой вопрос таков: как, черт возьми, этот потокобезопасен?

Я смотрел на класс BackgroundWorker в ILSpy - CancelAsync() просто устанавливает для cancellationPending значение true без использования барьера памяти, а CancellationPending просто возвращает cancellationPending без использования барьера памяти.

Согласно этой странице Джона Скита , вышеизложенное не поточно-ориентировано. Но документация для BackgroundWorker.CancellationPending гласит: «Это свойство предназначено для использования рабочим потоком, который должен периодически проверять CancellationPending и прерывать фоновую операцию, когда для нее установлено значение true."

Что здесь происходит? Это потокобезопасное или нет?

Ответы [ 4 ]

12 голосов
/ 06 августа 2011

Это потому, что BackgroundWorker наследует от Component, который наследует от MarshalByRefObject. Объект MBRO может находиться на другом компьютере или в другом процессе или домене приложения. Это работает, когда объект олицетворяется прокси, который имеет все те же свойства и методы, но чьи реализации маршалируют вызов по проводу.

Одним из побочных эффектов является то, что джиттер не может использовать встроенные методы и свойства, которые могут нарушить работу прокси. Что также предотвращает любые оптимизации, которые сохраняют значение поля в регистре процессора. Точно так же, как летучие.

8 голосов
/ 06 августа 2011

Это потокобезопасный.
Код

  while (!backgroundWorker.CancellationPending) 

читает свойство, и компилятор знает, что оно не может кэшировать результат.

И поскольку в обычной среде каждая запись совпадает с VolatileWrite, метод CancelAsync () может просто установить поле.

0 голосов
/ 07 августа 2011

Вопрос может быть интерпретирован двумя способами:

1) Является ли BackgroundWorker.CancellationPending реализация правильной?

Это не правильно, потому что это может привести к тому, что запрос на отмену будетнезамеченной.Реализация использует обычное чтение из поля поддержки.Если это вспомогательное поле обновляется другими потоками, тогда обновление может быть невидимым для кода чтения.Вот как выглядит реализация:

// non volatile variable:
private Boolean cancellationPending;

public Boolean CancellationPending {
    get {
        // ordinary read:
        return cancellationPending;
    }
}

Правильная реализация будет пытаться прочитать наиболее актуальное значение.Это может быть достигнуто путем объявления резервного поля «volatile», используя барьер памяти или блокировку.Возможно, есть и другие варианты, и некоторые из них лучше, чем другие, но зависит от команды, владеющей классом BackgroundWorker.

2) Является ли код, который использует BackgroundWorker.CancellationPending, правильным?

while (!backgroundWorker.CancellationPending) {
    DoSomething();
}

Этот код правильный.Цикл будет вращаться до тех пор, пока CancellationPending не вернет значение true.Помните, что свойства C # - это просто синтаксический сахар для методов CLR.На уровне IL это просто еще один метод, который будет выглядеть как «get_CancellationPending».Возвращаемые значения метода не кэшируются вызывающим кодом (возможно, потому что слишком сложно определить, есть ли у метода побочные эффекты).

0 голосов
/ 06 августа 2011

Хороший вопрос и очень справедливое сомнение. Звучит так, как будто не безопасно. Я думаю, что у MSDN тоже есть такое же сомнение, и именно поэтому он существует в BackgroundWorker.CancelAsync Method .

Внимание

Имейте в виду, что ваш код в обработчике событий DoWork может завершить работать как запрос отмены, и ваш цикл опроса может пропустить CancellationPending, установленный в true. В этом случае Отменен флаг System.ComponentModel.RunWorkerCompletedEventArgs в ваш обработчик событий RunWorkerCompleted не будет установлен в true, даже хотя запрос на отмену был сделан. Эта ситуация называется состояние гонки и является общей проблемой в многопоточном программировании. Для получения дополнительной информации о проблемах проектирования многопоточности см. Управляемый Потоки Best Practices.

Я не уверен насчет реализации класса BackgroundWorker. В этом случае важно посмотреть, как оно использует свойство BackgroundWorker.WorkerSupportsCancellation для внутреннего использования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...