Подтвердить что ты не робот

Как BackgroundWorker.CancellationPending потокобезопасен?

Способ отмены операции 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() просто устанавливает cancelationPending на true без использования барьера памяти, а CancellationPending просто возвращает cancelationPending без использования барьера памяти.

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

Что здесь происходит? Это поточно-безопасный или нет?

4b9b3361

Ответ 1

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

  while (!backgroundWorker.CancellationPending) 

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

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

Ответ 2

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

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

Ответ 3

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

Внимание

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

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

Ответ 4

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

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. Помните, что свойства С# - это просто синтаксический сахар для методов CLR. На уровне IL это еще один метод, который будет выглядеть как "get_CancellationPending". Возвращаемые значения метода не кэшируются при вызове кода (возможно, потому, что слишком сложно определить, имеет ли метод побочные эффекты).