https://www.codenong.com/cs106719464/ WinForm中的UI假死其實是個老生常談的問題了,但最近還是很多人問我該如何解決,所以今天就來說明一下如何解決UI假死的問題。實驗程式界面如下圖所示: 方法一:async + await + Task 首先看下麵一段代碼: ...
https://www.codenong.com/cs106719464/
WinForm中的UI假死其實是個老生常談的問題了,但最近還是很多人問我該如何解決,所以今天就來說明一下如何解決UI假死的問題。實驗程式界面如下圖所示:
方法一:async + await + Task
首先看下麵一段代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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); } // 一個耗時任務 private string GetMessage() { Thread.Sleep(10000); return "Hello World"; } } } |
在上面的代碼中,GetMessage()方法耗時10秒鐘,如果你點擊按鈕,那麼在10秒鐘內窗體將處於假死狀態。這種情況很常見,之所以會造成UI假死的原因也很簡單:某個函數耗時太久。在遇見這種情況的時候,我們就可以考慮使用async + await + Task來解決,代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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); } // 一個耗時任務 private async Task<string> GetMessage() { return await Task<string>.Run(() => { Thread.Sleep(10000); return "Hello World"; }); } } } |
運行之後點擊按鈕,你會發現UI沒有假死,窗體可以隨意拖動了。
方法二:使用BackgroundWorker組件
在很多時候,我們需要動態顯示當前的程式執行進度,以便讓用戶瞭解程式已經執行到哪一步了。很多同志都會這麼寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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); } } } } |
功能確實是實現了,進度條能夠顯示當前執行的進度,可惜UI還是處於假死狀態,所以用戶體驗還是不好。其實WinForm已經給我們提供了一個處理多線程任務的組件BackgroundWorker,使用它可以輕鬆讓你的程式告別UI假死,如下圖所示:
代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
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("完成"); } } } |
運行程式點擊按鈕,你會發現UI沒有處於假死狀態,窗體可以隨意拖動。
方法三:Task + 委托(回調函數)
首先需要明確一點:UI線程位於主線程,如果想要在子線程里更新UI狀態,必須要將其切換到主線程,最後進行更新操作。UI控制項一般會提供Invoke、InvokeRequired,其中InvokeRequired用於判斷是否有子線程在更新UI控制項,如果有則返回true,Invoke用於將控制權切換到UI線程,代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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的假死問題,當然也不一定要用Task,利用Thread也可以實現一樣的效果。