前言
WinForm中的UI假死其實(shí)是個(gè)老生常談的問題了,但最近還是很多人問我該如何解決,所以今天就來(lái)說明一下如何解決UI假死的問題。
實(shí)驗(yàn)程序界面如下圖所示:
正文
方法一 async + await + Task
首先看下面一段代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 開始
private void btnStart_Click(object sender, EventArgs e)
{
string message = GetMessage();
MessageBox.Show(message);
}
// 一個(gè)耗時(shí)任務(wù)
private string GetMessage()
{
Thread.Sleep(10000);
return "Hello World";
}
}
}
在上面的代碼中,GetMessage()方法耗時(shí)10秒鐘,如果你點(diǎn)擊按鈕,那么在10秒鐘內(nèi)窗體將處于假死狀態(tài)。
這種情況很常見,之所以會(huì)造成UI假死的原因也很簡(jiǎn)單:某個(gè)函數(shù)耗時(shí)太久。
在遇見這種情況的時(shí)候,我們就可以考慮使用async + await + Task來(lái)解決,代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 開始
private async void btnStart_Click(object sender, EventArgs e)
{
string message = await GetMessage();
MessageBox.Show(message);
}
// 一個(gè)耗時(shí)任務(wù)
private async Task<string> GetMessage()
{
return await Task<string>.Run(() =>
{
Thread.Sleep(10000);
return "Hello World";
});
}
}
}
運(yùn)行之后點(diǎn)擊按鈕,你會(huì)發(fā)現(xiàn)UI沒有假死,窗體可以隨意拖動(dòng)了。
方法二:使用BackgroundWorker組件
在很多時(shí)候,我們需要?jiǎng)討B(tài)顯示當(dāng)前的程序執(zhí)行進(jìn)度,以便讓用戶了解程序已經(jīng)執(zhí)行到哪一步了。
很多同志都會(huì)這么寫:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 開始
private void btnStart_Click(object sender, EventArgs e)
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
pgbStatus.Value++;
Thread.Sleep(1000);
}
}
}
}
功能確實(shí)是實(shí)現(xiàn)了,進(jìn)度條能夠顯示當(dāng)前執(zhí)行的進(jìn)度,可惜UI還是處于假死狀態(tài),所以用戶體驗(yàn)還是不好。
其實(shí)WinForm已經(jīng)給我們提供了一個(gè)處理多線程任務(wù)的組件BackgroundWorker,使用它可以輕松讓你的程序告別UI假死,如下圖所示:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.bgw.WorkerReportsProgress = true;
this.bgw.WorkerSupportsCancellation = true;
this.bgw.DoWork += DoWork;
this.bgw.ProgressChanged += ProgressChanged;
this.bgw.RunWorkerCompleted += RunWorkerCompleted;
}
// 開始
private void btnStart_Click(object sender, EventArgs e)
{
if (bgw.IsBusy)
{
return;
}
bgw.RunWorkerAsync();
}
// DoWork
private void DoWork(object sender, DoWorkEventArgs e)
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
bgw.ReportProgress(i);
Thread.Sleep(1000);
}
}
// ProgressChanged
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbStatus.Value = e.ProgressPercentage;
}
// RunWorkerCompleted
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("完成");
}
}
}
運(yùn)行程序點(diǎn)擊按鈕,你會(huì)發(fā)現(xiàn)UI沒有處于假死狀態(tài),窗體可以隨意拖動(dòng)。方法三:Task + 委托(回調(diào)函數(shù))首先需要明確一點(diǎn):UI線程位于主線程,如果想要在子線程里更新UI狀態(tài),必須要將其切換到主線程,最后進(jìn)行更新操作。UI控件一般會(huì)提供Invoke、InvokeRequired,其中InvokeRequired用于判斷是否有子線程在更新UI控件,如果有則返回true,Invoke用于將控制權(quán)切換到UI線程,代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 開始
private void btnStart_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
UpdateValue(i);
Thread.Sleep(1000);
}
});
}
// 處理線程
private void UpdateValue(int num)
{
if (pgbStatus.InvokeRequired)
{
pgbStatus.Invoke(new Action<int>(UpdateValue), new object[] { num });
}
else
{
pgbStatus.Value = num;
}
}
}
}
方法三也可以解決UI的假死問題,當(dāng)然也不一定要用Task,利用Thread也可以實(shí)現(xiàn)一樣的效果。通過上述方法,可以有效地解決 WinForms 應(yīng)用程序中 UI 假死的問題。選擇哪種方法取決于具體的場(chǎng)景和需求。BackgroundWorker 和 Task+ async/await 是最常用和推薦的方法,因?yàn)樗鼈兒?jiǎn)單易用且功能強(qiáng)大。ThreadPool和手動(dòng)創(chuàng)建線程則適用于需要更高靈活性的場(chǎng)景。無(wú)論選擇哪種方法,都要注意在更新 UI 時(shí)使用 Invoke或 BeginInvoke,以確保線程安全。如果你覺得這篇文章對(duì)你有幫助,不妨點(diǎn)個(gè)贊支持一下!你的支持是我繼續(xù)分享知識(shí)的動(dòng)力。如果有任何疑問或需要進(jìn)一步的幫助,歡迎隨時(shí)留言。也可以加入微信公眾號(hào)[DotNet技術(shù)匠] 社區(qū),與其他熱愛技術(shù)的同行一起交流心得,共同成長(zhǎng)!作者:碼農(nóng)家園
出處:codenong.com/cs106719464/聲明:網(wǎng)絡(luò)內(nèi)容,僅供學(xué)習(xí),尊重版權(quán),侵權(quán)速刪,歉意致謝!
該文章在 2024/12/28 11:58:25 編輯過