2012-04-26 5 views
4

В MSDN article о BackgroundWorker.CancelAsync метода, есть предостережение, которое говорит:Как избежать состояния гонки при использовании BackgroundWorker.CancelAsync?

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

Какое правильное и правильное решение, чтобы избежать этого Состояние гонки?

Вот мой пример кода, который провоцирует Apperance этого условия:

public partial class MainWindow : Window 
{ 
    BackgroundWorker bW; 
    int bWsCount = 0; 

    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    private void Window_MouseMove(object sender, MouseEventArgs e) 
    { 
     if (bW != null) 
      bW.CancelAsync(); 
     bW = new BackgroundWorker(); 
     bW.WorkerSupportsCancellation = true; 
     bW.DoWork += new DoWorkEventHandler(bW_DoWork); 
     bW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bW_RunWorkerCompleted); 
     bW.RunWorkerAsync(0); 
     bWsCount++; 
     labelBackgroundWorkersCount.Content = bWsCount; 
    } 

    void bW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 
     if (e.Cancelled || worker.CancellationPending) 
     { 
      bWsCount--; 
      labelBackgroundWorkersCount.Content = bWsCount; 
     } 
     else 
      worker.RunWorkerAsync(1000); 
    } 

    void bW_DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 
     int sleepDuration = Convert.ToInt32(e.Argument); 
     if (worker.CancellationPending) { e.Cancel = true; return; } 
     Thread.Sleep(sleepDuration); 
     if (worker.CancellationPending) { e.Cancel = true; return; } 
    } 
} 

ответ

4

Там нет никакого способа, чтобы избежать этого, не каждая резьб проблема имеет решение. Эти вещи можно обосновать, когда вы подталкиваете их до крайности. Представьте, что код в потоке пользовательского интерфейса вызывает CancelAsync() a nanosecond до того, как рабочий поток завершится. Так же, как поток выполняет свою последнюю инструкцию машинного кода. Ясно, что нет никакого способа, чтобы это могло быть обнаружено потоком. Также нет никакого способа, чтобы поток UI мог видеть, что поток находится на его последней инструкции.

Это не настоящая проблема, просто имейте это в виду, когда вы пишете обработчик события RunWorkerCompleted. Если свойство e.Cancelled является ложным, тогда работник все равно завершается. Если необходимо, вы можете просто & & с другим bool, который вы установили в true, когда вы вызываете CancelAsync(). Грубо говоря:

private bool cancelRequested; 

    private void CancelButton_Click(object sender, EventArgs e) { 
     cancelRequested = true; 
     backgroundWorker1.CancelAsync(); 
    } 

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
     if (e.Error != null) throw e.Error; 
     if (!e.Cancelled && !cancelRequested) { 
      // Completed entirely normally 
      //... 
     } 
    } 

Пожалуйста сделать иметь в виду, что это не просто гонки в коде. Более практичная проблема заключается в том, что это гонка в мозгу пользователя. В режиме сбоя все заканчивается обычно миллисекундой, прежде чем пользователь нажмет кнопку «Отмена».

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

+0

Спасибо за разъяснение! :) – Matvienko

+0

Мне нравится этот ответ. Однако, не является ли исходный код по существу этим, проверяя «worker.CancellationPending»? Пока 'CancelAsync' только когда-либо вызывается в потоке ui, а' worker.CancellationPending' проверяется на нитке ui, вы избегаете условия гонки. Правильно? –

+1

@Matt - CancellationPending сбрасывается до запуска RunWorkerCompleted. –

Смежные вопросы