最近一個項目需求中的一個功能是需要用progressBar反映處理文件的進度。
研究了Invoke和BeginInvoke方法。
Control.Invoke 方法 (Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委托。
Control.BeginInvoke 方法 (Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委托。
我開始的想法是開一個線程處理,代碼如下:
private delegate void DoDataDelegate(object number);
private void button2_Click(object sender, EventArgs e)
{
// Thread myThread = new Thread(DoData);
Thread myThread = new Thread(new ParameterizedThreadStart(DoData));
myThread.IsBackground = true;
myThread.Start(int.Parse(textBox1.Text)); //線程開始
Console.WriteLine("主線程繼續執行---------------");
}
private void DoData(object number)
{
if (progressBar1.InvokeRequired)
{
Console.WriteLine("開始");
DoDataDelegate d = DoData;
progressBar1.BeginInvoke(d, number);
//代碼段D
}
else
{
progressBar1.Maximum = (int)number;
Console.WriteLine("准備進行循環調用");
for (int i = 0; i < (int)number; i++)
{
//這里是一段耗時長的代碼
progressBar1.Value = i + 1;
}
}
}
在上述代碼中當執行到progressBar1.BeginInvoke(d, number);時,myThread封送消息給UI,然后自己繼續執行代碼段D,代碼段D的執行相對於myThread是異步的。如果將
progressBar1.BeginInvoke(d, number);換成progressBar1.Invoke(d, number);其余的代碼不變,那么代碼段D將會等到線程myThread結束后才會執行。無論是調用BeginInvoke或者Invoke,因為調用者是progressBar1,所以更新progressBar1.Value時,是在"擁有此控件的基礎窗口句柄的線程(UI線程)上執行指定的委托",在更新value值之前有一段耗時長的代碼,所以此時UI線程會阻塞,處於假死狀態。
我的解決辦法是使用BackgroundWorker 類。
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += DoWork_Handler;
backgroundWorker1.ProgressChanged += ProgressChanged_Handler;
private void button2_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
//這里省略一段執行耗時的操作
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
}
BackgroundWorker.RunWorkerAsync是創建了一個線程處理耗時的操作,backgroundWorker1_DoWork方法執行,執行到backgroundWorker1.ReportProgress(i);會觸發backgroundWorker1_ProgressChanged方法的執行,在backgroundWorker1_ProgressChanged方法中,更新value的值。這樣做UI界面和線程處理好使的工作是異步的,所以不會造成UI界面卡死的現象。
我了解了Invoke 和BeginInvoke的原理,但不知道怎么用他們實現更新ProgressBar的值,並保證UI界面不出現假死。所以我選擇了BackgroundWorker解決了我的問題。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
再次更新。
經過和老師的討論,我自己進行了實踐。如果用ProgressBar反映處理耗時的程序的進度。可以開一個子線程處理耗時的部分,然后設置一個全局變量標識耗時的程序處理的進度。
代碼段簡化如下:
int num=0;
int number=16;
private void button2_Click(object sender, EventArgs e)
{
progressBar1.Maximum = number;
// Thread myThread = new Thread(DoData);
Thread myThread = new Thread(new ParameterizedThreadStart(DoData));
myThread.IsBackground = true;
myThread.Start(number); //線程開始
}
private void DoData(object number)
{
Console.WriteLine("准備進行循環調用");
for (int i = 0; i < (int)number; i++)
{
//這里是一段耗時長的代碼
num = i + 1;
}
}
}
然后加入一個Timer每隔一定的時間訪問num的值。在Timer_tick函數中更新progressBar的value的值。
補充:在C#中的子線程中不能直接更新UI中的控件值。可以通過
1.Invoke或者BeginInvoke的方式調用委托的方法
2.使用BackGroundWorker類取代Thread類進行異步操作
3.通過設置窗體屬性,取消線程安全檢查來“避免跨線程操作異常”。(非線程安全,不建議使用)
4.通過UI線程的SynchronizationContext的Post/Send方法更新。
