我有兩個類,Apple
和Dog
。這兩個類都有一個在啓動時調用的方法,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.ProgressChanged
和Dog.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 <= 100
和progressBar.Maximum = 100
。
儘管您應該注意到BackgroundWorker將在創建控件的線程上提升ProgressChanged,但您的底部假設是正確的,因此從worker線程調用backgroundWorker1.ReportProgress將調用UI線程上的ProgressChanged處理程序(假設這是你創建backgroundworker的地方),所以並非所有事情都是同步發生的。這也意味着您的私有SetProgress方法中的Invoke是不必要的。 – roken
_apple上的DoLotsOfWork實際上需要很長時間嗎?如果不是,你不會注意到狀態文本中的不同。做Thread.Sleep(1000)應該讓轉換更明顯(當前你的睡眠被註釋掉了)。 – roken
@roken:* [「對ReportProgress方法的調用是異步的並立即返回。」](http://msdn.microsoft.com/en-us/library/ka89zff4.aspx)* - 我明白了。這可能是我的問題。然後我需要編輯'_apple/_dog。ProgressChanged'處理程序在一些volatile布爾值上循環等待,直到'backgroundWorker1_ProgressChanged'已經完成*(或使用Semaphore)*?還是有更好的,非阻塞的方式? –