C#多线程与UI响应 防止界面假死不响应(子线程创建的窗体获取消息响应用Application.DoEvent )
一.
概述
在使用C#進(jìn)行應(yīng)用程序設(shè)計(jì)時(shí),經(jīng)常會(huì)采用多線程的方式進(jìn)行一些后臺(tái)任務(wù)的工作。對(duì)于不同的應(yīng)用場(chǎng)景,使用的策略也不盡相同。
1.
后臺(tái)循環(huán)任務(wù),少量UI更新:例如批量上傳文件,并提供進(jìn)度。這種情況使用BackgroundWorker組件是非常好的選擇。
2.
耗時(shí)的后臺(tái)任務(wù):這里的耗時(shí)任務(wù)是指一個(gè)時(shí)間較長的任務(wù),并且不能精確獲取進(jìn)度,如:調(diào)用一個(gè)遠(yuǎn)程WebService接口。這種情況可以開兩個(gè)線程,一個(gè)工作,一個(gè)更新UI(不能提供進(jìn)度,只能顯示動(dòng)畫表示系統(tǒng)在運(yùn)行中)。
3.
耗時(shí)的UI任務(wù):當(dāng)工作壓力集中在UI響應(yīng)上時(shí),可以在工作者線程中增加延時(shí),從而讓UI線程獲得響應(yīng)時(shí)間。整個(gè)工作的總體時(shí)間會(huì)增加,但用戶響應(yīng)效果會(huì)好很多。
二.
后臺(tái)的循環(huán)任務(wù),少量UI更新
這種情況使用BackgroundWorker組件是最好的選擇。(詳見附一)
三.
后臺(tái)耗時(shí)任務(wù)
在后臺(tái)執(zhí)行一個(gè)不可分解的耗時(shí)任務(wù),需要進(jìn)行界面更新,以便讓客戶看上去程序有所響應(yīng)。這種情況下,UI線程一般也不知道工作線程何時(shí)結(jié)束,所以一般執(zhí)行循環(huán)任務(wù),當(dāng)工作線程結(jié)束后,關(guān)閉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;
?
}
這里只要注意一點(diǎn):線程調(diào)用的方法都不能訪問用戶控件,必須通過委托調(diào)用Form的方法來實(shí)現(xiàn)界面更新。
四.
耗時(shí)的UI任務(wù)
當(dāng)整個(gè)工作壓力集中在UI響應(yīng)上時(shí),可以在工作者線程中增加延時(shí),從而讓UI線程獲得響應(yīng)時(shí)間。整個(gè)工作的總體時(shí)間會(huì)增加,但用戶響應(yīng)效果會(huì)好很多。
?
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);//模擬耗時(shí)UI任務(wù),非循環(huán),不可分解
?
}
五.
補(bǔ)充
1.
Invoke?和?BeginInvoke
在多線程編程中,我們經(jīng)常要在工作線程中去更新界面顯示,而在多線程中直接調(diào)用界面控件的方法是錯(cuò)誤的做法,正確的做法是將工作線程中涉及更新界面的代碼封裝為一個(gè)方法,通過?Invoke?或者?BeginInvoke?去調(diào)用,兩者的區(qū)別就是一個(gè)導(dǎo)致工作線程等待,而另外一個(gè)則不會(huì)。
而所謂的“一面響應(yīng)操作,一面添加節(jié)點(diǎn)”永遠(yuǎn)只能是相對(duì)的,使?UI?線程的負(fù)擔(dān)不至于太大而以,因?yàn)榻缑娴恼_更新始終要通過?UI?線程去做,我們要做的事情是在工作線程中包攬大部分的運(yùn)算,而將對(duì)純粹的界面更新放到?UI?線程中去做,這樣也就達(dá)到了減輕?UI?線程負(fù)擔(dān)的目的了。
2.
Application.DoEvent
在耗時(shí)的循環(huán)的UI更新的方法中,插入Application.DoEvent,會(huì)使界面獲得響應(yīng),Application.DoEvent會(huì)調(diào)用消息處理程序。
?
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)
{
}
等價(jià)與
?
try
?
?
{
?
?
Monitor.Enter(object);
?
?
}
?
?
finally
?
?
{
?
?
Monitor.Exit(object)
?
}
附一:
?
BackgroundWorker組件使用說明
?
一.
概述
BackgroundWorker是·NET 2.0提供的一個(gè)多線程組件,在應(yīng)用程序中使用,可以非常簡單方便地實(shí)現(xiàn)UI控件通信,并自動(dòng)處理多線程沖突問題。
二.
基本屬性
?
1.
WorkerReportsProgress?,bool:是否允許報(bào)告進(jìn)度;
?
?
2.
WorkerSupportsCancellation,bool:是否允許取消線程。
?
?
3.
CancellationPending,bool,get:讀取用戶是否取消該線程。
?
?
?
?
三.
基本事件
?
1.
DoWork:工作者線程
?
?
2.
RunWorkerCompleted?:線程進(jìn)度報(bào)告
?
?
3.
ProgressChanged:線程結(jié)束報(bào)告
?
四.
基本方法
?
1.
RunWorkerAsync()?:啟動(dòng)工作者線程;
?
?
2.
CancelAsync():取消工作者線程;
?
?
3.
ReportProgress(int);
報(bào)告進(jìn)度
?
五.
代碼
?
//啟動(dòng)
?
?
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);
//干點(diǎn)實(shí)際的事
?
?
?
?
?
backgroundWorker.ReportProgress(i);
//報(bào)告進(jìn)度
?
?
}
?
?
}
?
?
?
?
?
//線程進(jìn)度報(bào)告
?
?
private?void?backgroundWorker_ProgressChanged(object?sender,?ProgressChangedEventArgs?e)
?
?
{
?
?
this.progressBar1.Value = e.ProgressPercentage * 100 / 150;
?
?
}
?
?
?
?
?
//線程結(jié)束報(bào)告
?
?
private?void?backgroundWorker_RunWorkerCompleted(object?sender,?RunWorkerCompletedEventArgs?e)
?
?
{
?
?
this.btnStart.Enabled =?true;
?
?
this.btnStop.Enabled =?false;
?
}
總結(jié)
以上是生活随笔為你收集整理的C#多线程与UI响应 防止界面假死不响应(子线程创建的窗体获取消息响应用Application.DoEvent )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker启动,重启,关闭命令
- 下一篇: c#中通过截获windows消息禁止改变