2012-05-09 78 views
3

我有兩個類,AppleDog。這兩個類都有一個在啓動時調用的方法,DoLotsOfWork()發佈了一個事件ProgressChanged使用事件和BackgroundWorker更新UI導致的競爭條件

public class Apple 
{ 
    //For progress bars 
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged; 
    private void OnProgressChanged(double progress) 
    { 
     if(ProgressChanged != null) 
      ProgressChanged(this, new ProgressChangedEventArgs((int)(progress * 100), null)); 
    } 

    //Takes a long time to run 
    public void DoLotsOfWork() 
    { 
     for(lots of things) 
     { 
      ... 
      OnProgressChanged(percentage); 
     } 
    } 
} 
//Dog is similar 

爲了防止UI被鎖住,我使用BackgroundWorker來運行這些UI。我有Apple.ProgressChangedDog.ProgressChanged請致電BackgroundWorker.ReportProgress(它調用BackgroundWorker.ProgressChanged事件)更新標籤和進度欄,以便用戶知道發生了什麼。

public class MainForm : Form 
{ 
    private Apple _apple; 
    private Dog _dog; 
    private bool _isAppleCompleted; 

    ... 

    //Set the ProgressChanged callbacks and start the BackgroundWorker 
    private void MainForm_Load(object sender, EventArgs e) 
    { 
     _apple.ProgressChanged += (a, args) => backgroundWorker1.ReportProgress(args.ProgressPercentage); 
     _dog.ProgressChanged += (a, args) => backgroundWorker1.ReportProgress(args.ProgressPercentage); 
     backgroundWorker1.RunWorkerAsync(); 
    } 

    //Invoke the UI thread to set the status/progress 
    private void SetStatus(string status) 
    { 
     lblStatus.Invoke((Action)(() => lblStatus.Text = status)); 
    } 
    private void SetProgress(int progress) 
    { 
     progressBar.Invoke((Action)(() => progressBar.Value = progress)); 
    } 

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     _isAppleCompleted = false; 
     SetStatus("Apple (Step 1/2)"); 
     _apple.DoLotsOfWork(); 

     //Thread.Sleep(1); //This *sometimes* fixes the problem!? 

     _isAppleCompleted = true; 
     SetStatus("Dog (Step 2/2)"); 
     _dog.DoLotsOfWork(); 
    } 

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     //_apple.DoLotsOfWork should cause the progress bar to go from 0 to 50 
     //_dog.DoLotsOfWork should cause the progress bar to go from 50 to 100 
     int progress = (_isAppleCompleted ? 50 : 0) + e.ProgressPercentage/2; 
     SetProgress(progress); 
    } 

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     //stuff 
    } 
} 

我希望發生:文本「蘋果(步驟1/2)」從0%的進度條移動到50%。然後當進度條從50%移動到100%時,會顯示「狗(步驟2/2)」這個短語。

實際發生了什麼:只顯示文本「Dog(Step 2/2)」。進度條從0%上升到100%,然後回到50%並上升到100%。


因爲我認爲處理程序是同一個線程中事件的主叫方運行事件;和我認爲Control.Invoke()阻塞,直到Action完成,我看不出有什麼競爭條件,因爲一切都基本上同步發生。 有沒有人知道爲什麼會發生這種情況,以及如何解決這個問題?

是的,我檢查了0 <= e.ProgressPercentage <= 100progressBar.Maximum = 100

+0

儘管您應該注意到BackgroundWorker將在創建控件的線程上提升ProgressChanged,但您的底部假設是正確的,因此從worker線程調用backgroundWorker1.ReportProgress將調用UI線程上的ProgressChanged處理程序(假設這是你創建backgroundworker的地方),所以並非所有事情都是同步發生的。這也意味着您的私有SetProgress方法中的Invoke是不必要的。 – roken

+0

_apple上的DoLotsOfWork實際上需要很長時間嗎?如果不是,你不會注意到狀態文本中的不同。做Thread.Sleep(1000)應該讓轉換更明顯(當前你的睡眠被註釋掉了)。 – roken

+0

@roken:* [「對ReportProgress方法的調用是異步的並立即返回。」](http://msdn.microsoft.com/en-us/library/ka89zff4.aspx)* - 我明白了。這可能是我的問題。然後我需要編輯'_apple/_dog。ProgressChanged'處理程序在一些volatile布爾值上循環等待,直到'backgroundWorker1_ProgressChanged'已經完成*(或使用Semaphore)*?還是有更好的,非阻塞的方式? –

回答

1

你在底部假設是正確的,但應注意的是,BackgroundWorker的是要提高ProgressChanged線程的控制上被創建,因此調用工作線程的backgroundWorker1.ReportProgress調用UI線程上的ProgressChanged處理程序(假設這是您創建backgroundworker的地方),所以並非所有事情都是同步發生的。這也意味着您的私有SetProgress方法中的Invoke是不必要的。

0

通過調用你的後臺工作者的Invoke()方法,你打敗了擁有後臺工作者的目的。要更新進度: 1. BackgroundWorker應調用ReportProgress方法。
在在WinForm BackgroundWorker的控制 2,添加一個ProgressChanged處理程序來更新你的進步

+0

我不明白如何使用'Invoke()'*「來打敗擁有後臺工作者的目的。」*你的建議正是我正在做的。 –

+0

@ BlueRaja-DannyPflughoeft正如我在我對這個問題的評論中所說的那樣,BackgroundWorker旨在在UI線程上引發它的事件,以便您不必自己調用該調用。 – roken

+0

後臺工作者應該引發在UI中處理的事件,而不是直接操縱UI元素 – qwerty13579