C#多线程与UI响应 防止界面假死不响应(子线程创建的窗体获取消息响应用Application.DoEvent )
一.
概述
在使用C#進行應用程序設計時,經常會采用多線程的方式進行一些后臺任務的工作。對于不同的應用場景,使用的策略也不盡相同。
1.
后臺循環任務,少量UI更新:例如批量上傳文件,并提供進度。這種情況使用BackgroundWorker組件是非常好的選擇。
2.
耗時的后臺任務:這里的耗時任務是指一個時間較長的任務,并且不能精確獲取進度,如:調用一個遠程WebService接口。這種情況可以開兩個線程,一個工作,一個更新UI(不能提供進度,只能顯示動畫表示系統在運行中)。
3.
耗時的UI任務:當工作壓力集中在UI響應上時,可以在工作者線程中增加延時,從而讓UI線程獲得響應時間。整個工作的總體時間會增加,但用戶響應效果會好很多。
二.
后臺的循環任務,少量UI更新
這種情況使用BackgroundWorker組件是最好的選擇。(詳見附一)
三.
后臺耗時任務
在后臺執行一個不可分解的耗時任務,需要進行界面更新,以便讓客戶看上去程序有所響應。這種情況下,UI線程一般也不知道工作線程何時結束,所以一般執行循環任務,當工作線程結束后,關閉UI線程就可以了。
?
Thread?uithread =?null;
?
?
private?void?btnStart_Click(object?sender,?EventArgs?e)
?
?
{
?
?
uithread =?new?Thread(new?ThreadStart(this.UpdateProgressThread));
?
?
uithread.Start();
?
?
?
?
?
Thread?workthread =?new?Thread(new?ThreadStart(this.DoSomething));
?
?
workthread.Start();
?
?
}
?
?
?
?
?
private?void?DoSomething()
?
?
{
?
?
Thread.Sleep(5000);
?
?
uithread.Abort();
?
?
MessageBox.Show("work end");
?
?
}
?
?
?
?
?
private?void?UpdateProgressThread()
?
?
{
?
?
for?(int?i = 0; i < 10000; i++)
?
?
{
?
?
Thread.Sleep(100);
?
?
this.Invoke(new?Action<int>(this.UpdateProgress), i);
?
?
}
?
?
}
?
?
?
?
?
private?void?UpdateProgress(int?v)
?
?
{
?
?
this.progressBar1.Value = v;
?
}
這里只要注意一點:線程調用的方法都不能訪問用戶控件,必須通過委托調用Form的方法來實現界面更新。
四.
耗時的UI任務
當整個工作壓力集中在UI響應上時,可以在工作者線程中增加延時,從而讓UI線程獲得響應時間。整個工作的總體時間會增加,但用戶響應效果會好很多。
?
private?void?FormInitForm_Load(object?sender,?EventArgs?e)
?
?
{
?
?
this.listView1.Items.Clear();
?
?
Thread?workthread =?new?Thread(new?ThreadStart(this.DoSomething));
?
?
workthread.Start();
?
?
}
?
?
?
?
?
private?void?DoSomething()
?
?
{
?
?
for?(int?i = 0; i < 30; i++)
?
?
{
?
?
this.Invoke(new?Action<int>(this.LoadPicture), i);
?
?
Thread.Sleep(100);
?
?
}
?
?
}
?
?
?
?
?
private?void?LoadPicture(int?i)
?
?
{
?
?
string?text =?string.Format("Item{0}", i);
?
?
ListViewItem?lvi =?new?ListViewItem(text, 0);
?
?
this.listView1.Items.Add(lvi);
?
?
Thread.Sleep(200);//模擬耗時UI任務,非循環,不可分解
?
}
五.
補充
1.
Invoke?和?BeginInvoke
在多線程編程中,我們經常要在工作線程中去更新界面顯示,而在多線程中直接調用界面控件的方法是錯誤的做法,正確的做法是將工作線程中涉及更新界面的代碼封裝為一個方法,通過?Invoke?或者?BeginInvoke?去調用,兩者的區別就是一個導致工作線程等待,而另外一個則不會。
而所謂的“一面響應操作,一面添加節點”永遠只能是相對的,使?UI?線程的負擔不至于太大而以,因為界面的正確更新始終要通過?UI?線程去做,我們要做的事情是在工作線程中包攬大部分的運算,而將對純粹的界面更新放到?UI?線程中去做,這樣也就達到了減輕?UI?線程負擔的目的了。
2.
Application.DoEvent
在耗時的循環的UI更新的方法中,插入Application.DoEvent,會使界面獲得響應,Application.DoEvent會調用消息處理程序。
?
private?void?button2_Click(object?sender,?EventArgs?e)
?
?
{
?
?
for?(int?i = 0; i < 30; i++)
?
?
{
?
?
string?text =?string.Format("Item{0}", i);
?
?
ListViewItem?lvi =?new?ListViewItem(text, 0);
?
?
this.listView1.Items.Add(lvi);
?
?
Thread.Sleep(200);
?
?
for?(int?j = 0; j < 10; j++)
?
?
{
?
?
Thread.Sleep(10);
?
?
Application.DoEvents();
?
?
}
?
?
}
?
}
3.
Lock
lock(object)
{
}
等價與
?
try
?
?
{
?
?
Monitor.Enter(object);
?
?
}
?
?
finally
?
?
{
?
?
Monitor.Exit(object)
?
}
附一:
?
BackgroundWorker組件使用說明
?
一.
概述
BackgroundWorker是·NET 2.0提供的一個多線程組件,在應用程序中使用,可以非常簡單方便地實現UI控件通信,并自動處理多線程沖突問題。
二.
基本屬性
?
1.
WorkerReportsProgress?,bool:是否允許報告進度;
?
?
2.
WorkerSupportsCancellation,bool:是否允許取消線程。
?
?
3.
CancellationPending,bool,get:讀取用戶是否取消該線程。
?
?
?
?
三.
基本事件
?
1.
DoWork:工作者線程
?
?
2.
RunWorkerCompleted?:線程進度報告
?
?
3.
ProgressChanged:線程結束報告
?
四.
基本方法
?
1.
RunWorkerAsync()?:啟動工作者線程;
?
?
2.
CancelAsync():取消工作者線程;
?
?
3.
ReportProgress(int);
報告進度
?
五.
代碼
?
//啟動
?
?
private?void?btnStart_Click(object?sender,?EventArgs?e)
?
?
{
?
?
this.btnStart.Enabled =?false;
?
?
this.btnStop.Enabled =?true;
?
?
?
?
?
this.backgroundWorker.RunWorkerAsync();
?
?
}
?
?
?
?
?
//通知線程停止
?
?
private?void?btnStop_Click(object?sender,?EventArgs?e)
?
?
{
?
?
this.backgroundWorker.CancelAsync();
?
?
}
?
?
?
?
?
//工作者線程
?
?
private?void?backgroundWorker_DoWork(object?sender,?DoWorkEventArgs?e)
?
?
{
?
?
for?(int?i = 0; i < 150; i++)
?
?
{
?
?
if?(backgroundWorker.CancellationPending)
//查看用戶是否取消該線程
?
?
{
?
?
break;
?
?
}
?
?
?
?
?
System.Threading.Thread.Sleep(50);
//干點實際的事
?
?
?
?
?
backgroundWorker.ReportProgress(i);
//報告進度
?
?
}
?
?
}
?
?
?
?
?
//線程進度報告
?
?
private?void?backgroundWorker_ProgressChanged(object?sender,?ProgressChangedEventArgs?e)
?
?
{
?
?
this.progressBar1.Value = e.ProgressPercentage * 100 / 150;
?
?
}
?
?
?
?
?
//線程結束報告
?
?
private?void?backgroundWorker_RunWorkerCompleted(object?sender,?RunWorkerCompletedEventArgs?e)
?
?
{
?
?
this.btnStart.Enabled =?true;
?
?
this.btnStop.Enabled =?false;
?
}
總結
以上是生活随笔為你收集整理的C#多线程与UI响应 防止界面假死不响应(子线程创建的窗体获取消息响应用Application.DoEvent )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker启动,重启,关闭命令
- 下一篇: 解决:com.mysql.jdbc.My