WinForm中UI假死的解決方法

前言

WinForm中的UI假死其實是個老生常談的問題了,但最近還是很多人問我該如何解決,所以今天就來說明一下如何解決UI假死的問題。實驗程序界面如下圖所示:
在這裏插入圖片描述

方法一: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);
        }

        // 一個耗時任務
        private string GetMessage()
        {
            Thread.Sleep(10000);
            return "Hello World";
        }
    }
}

在上面的代碼中,GetMessage()方法耗時10秒鐘,如果你點擊按鈕,那麼在10秒鐘內窗體將處於假死狀態。這種情況很常見,之所以會造成UI假死的原因也很簡單:某個函數耗時太久。在遇見這種情況的時候,我們就可以考慮使用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 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組件

在很多時候,我們需要動態顯示當前的程序執行進度,以便讓用戶瞭解程序已經執行到哪一步了。很多同志都會這麼寫:

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假死,如下圖所示:
在這裏插入圖片描述
代碼如下:

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控件,如果有則返回trueInvoke用於將控制權切換到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的假死問題,當然也不一定要用Task,利用Thread也可以實現一樣的效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章