C# WinForm 最簡單解決 UI 介面卡死問題 非同步教學

在寫 WinForm 應用程式的時候,只要遇到程式需要處理比較久的情況,介面就會卡住、按鈕無法點擊、視窗無法拖曳,甚至是應用程式沒有回應,要等到程式處理完才可以恢復。
執行到 6000 左右程式就會卡住


為什麼會這樣呢?

因為負責處理 UI (使用者介面)的主執行序被叫去做複雜的運算,所以沒有空管 UI 的事情,要等他把當前的事情做完,才會回來更新使用者介面,介面才會有反應。

先看一個最簡單的案例(註: 為了方便說明,程式的部分和 gif 中的範例不同),按下按鈕後 Label 顯示「處理中」,使用等待三秒模擬複雜的程式邏輯處理需要的時間,然後再將 Label 顯示為「完成」
    
private void Button_Click(object sender, RoutedEventArgs e)
{
	label.Text = "處理中";

	// 這裡代表複雜的運算
    Thread.Sleep(3000); // 等待 3 秒 

	label.Text = "完成";
}
    

看起來沒有什麼問題,但是執行上面的程式碼後會發現按下按鈕後 Label 不會顯示「處理中」,而是介面卡住,然後 3 秒後直接顯示「完成」。

Label 不會更新是因為被判斷在這個方法中 Label 改為「處理中」後會被直接改為「完成」,所以就很「聰明」的直接等後面要顯示「完成」的時候直接顯示「完成」。

稍微提一下,如果這裡想要顯示「處理中」,我們可以加上 Refresh 讓他馬上更新,不過如果後面使用非同步之後就不會需要用到這行了
    

private void Button_Click(object sender, RoutedEventArgs e)
{
	label.Text = "處理中";
    label.Refresh();

	// 這裡代表複雜的運算
    Thread.Sleep(3000); // 等待 3 秒 

	label.Text = "完成";
}
    

如果想要讓主執行序可以專心的處理 UI ,那我們應該要把程式主要處理的邏輯放到背景,就是使用不同的執行序來處理,也就是非同步
    

private void Button_Click(object sender, RoutedEventArgs e)
{
	label.Text = "處理中";
    label.Refresh();
            
	Task.Run(() =>
    {
    	// 這裡代表複雜的運算
    	Thread.Sleep(3000); // 等待 3 秒 
	});

label.Text = "完成";
}
    

太棒了,問題解決了,不會再卡住了!
等等,不對阿,怎麼沒有等待 3 秒了?

原來是整個 Task.Run 裡面的程式交由其他執行序處理,所以在安排好其他執行序處理後就直接執行後面的程式讓 Label 顯示「完成」了。

所以可以使用 await 等待 Task.Run 處理結束後再繼續執行上下的程式,也因為使用 await ,所以要在方法上面加上 async,說明使用非同步來處理此方法
    

private async void Button_Click(object sender, RoutedEventArgs e)
{
	label.Text = "處理中";
    label.Refresh();
            
	await Task.Run(() =>
    {
    	// 這裡代表複雜的運算
    	Thread.Sleep(3000); // 等待 3 秒 
	});

label.Text = "完成";
}
    

留言