Отмена BackgroundWorker, как предотвратить гонку, когда она уже закончилась - PullRequest
5 голосов
/ 08 января 2010

Я использую BackgroundWorker для выполнения длинных вычислений (только одно такое вычисление одновременно).

У пользователя есть возможность отменить работника (вызывая worker.CancelAsync).

В методе worker.DoWork я периодически проверяю флаг ожидания отмены и затем возвращаюсь из метода.

Затем с работника возникает событие Completed, и я могу проверить, что работник был отменен. Кроме того, и это важно, я выполняю дополнительную очистку при обнаружении отмены.

Я уверен, что может возникнуть проблема, если пользователь отменит работника, и он уже возвращен из метода DoWork. В таком случае мне бы очень хотелось знать, что работник был уволен, чтобы я мог убрать ...

Есть ли лучший способ справиться с процедурой отмены с очисткой рабочего?

Ответы [ 5 ]

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

Я не думаю, что другие ответы здесь действительно решают возможное состояние гонки во всех случаях. Проблема в том, что CancellationPending, по-видимому, возвращается в значение false после завершения DoWork независимо от того, действительно ли DoWork получил шанс проверить флаг. Вы можете проверить это сами, если хотите. Чтобы обойти эту проблему, мне нужно было создать подкласс, например:

Public Class CancelTrackingBackgroundWorker
  Inherits System.ComponentModel.BackgroundWorker
  Public CancelRequested As Boolean = False
  Public Sub TrackableCancelAsync()
    Me.CancelRequested = True
    Me.CancelAsync()
  End Sub
End Class

(Кстати, раздражающая CancelAsync не может быть переопределена.) Затем я вызываю TrackableCancelAsync вместо старой CancelAsync и проверяю наличие этого нового флага CancelRequested в моем коде. Пока вы только звоните и проверяете из потока пользовательского интерфейса (как должно быть стандартным), это должно решать ваши проблемы с потоками.

2 голосов
/ 09 января 2010

Ваш обработчик событий DoWork должен периодически проверять BackgroundWorker.CancellationPending и устанавливать DoWorkEventArgs.Cancel в true перед возвратом, если он был отменен.

Ваш обработчик событий RunWorkerCompleted должен проверить свойство RunWorkerCompletedEventArgs.Cancelled, чтобы определить, отменен ли обработчик событий DoWork (установите для DoWorkEventArgs.Cancel значение true).

В случае состояния гонки может случиться так, что пользователь запросил отмену (BackgroundWorker.CancellationPending верно), но работник не увидел его (RunWorkerCompletedEventArgs.Cancelled неверно). Вы можете проверить эти два свойства, чтобы определить, что это произошло, и делать все, что вы выберете (либо рассматривать его как успешное завершение - потому что работник действительно закончил успешно, либо как отмену - потому что пользователь отменил и не заботится больше).

Я не вижу ситуации, в которой есть какая-то двусмысленность в том, что произошло.

EDIT

В ответ на комментарий - если существует несколько классов, которые должны обнаружить CancellationPending, на самом деле нет никакой альтернативы передаче этим классам ссылки на тип, такой как BackgroundWorker, который позволяет им получать эту информацию. Вы можете абстрагировать это в интерфейс, что я обычно делаю, как описано в этом ответе на вопрос о BackgroundWorkers. Но вам все равно нужно передать ссылку на тип, который реализует этот интерфейс, вашим рабочим классам.

Если вы хотите, чтобы ваши рабочие классы могли устанавливать DoWorkEventArgs.Cancel, вам нужно будет либо передать ссылку на это, либо принять другое соглашение (например, логическое возвращаемое значение или пользовательское исключение), которое позволяет вашим рабочим классам указать, что отмена произошла.

1 голос
/ 09 января 2010

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

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
  var bgw = sender as BackgroundWorker;
  if (e.Cancelled || bgw.CancellationPending) {
    // etc..
  }
}
0 голосов
/ 08 января 2010

Не видя ваш код, довольно сложно вносить предложения, но одну вещь, которую вы можете сделать, это отключить кнопку «Отмена», когда пользователь щелкает ее, а также при выходе из метода DoWork.

Если пользователь также может отменить с помощью нажатия клавиши, вам также необходимо отключить это.

Также - если метод DoWork завершен, пользователю нечего «отменить» - или я что-то упустил. В этом случае, даже если вы не отключите кнопку отмены, вам ничего не нужно делать.

Зачем вам нужно видеть флаг отмены после того, как работник сделал свою работу? Вы все еще хотите откатить то, что когда-либо было изменено?

0 голосов
/ 08 января 2010

Есть ли причина, по которой вы не можете выполнить очистку в конце метода, который вы запускаете асинхронно?

Обычно я использую такую ​​структуру

private void MyThreadMethod()
{
    try
    {
        // Thread code
    }
    finally 
    {
        // Cleanup
    }
}

Я использую событие complete только для выполнения таких задач, как обновление пользовательского интерфейса, а не для очистки после потока.

...