在 GUI 中執行非同步操作 序 目錄 一、在 GUI 程式中執行非同步操作 下麵通過窗體示例演示以下操作-點擊按鈕後:①將標簽內容改成:“Doing”,並將按鈕禁用(表示執行中);②線程掛起3秒(模擬耗時操作);③將標簽內容改為:“Complete”,並啟用按鈕(表示執行完成); 可是執行結果卻是: ...
在 GUI 中執行非同步操作
序
目錄
一、在 GUI 程式中執行非同步操作
下麵通過窗體示例演示以下操作-點擊按鈕後:①將標簽內容改成:“Doing”,並將按鈕禁用(表示執行中);②線程掛起3秒(模擬耗時操作);③啟用按鈕,將標簽內容改為:“Complete”(表示執行完成);
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 private void btnDo_Click(object sender, EventArgs e) 9 { 10 btnDo.Enabled = false; 11 lblText.Text = @"Doing"; 12 13 Thread.Sleep(3000); 14 15 btnDo.Enabled = true; 16 lblText.Text = @"Complete"; 17 } 18 }
可是執行結果卻是:
圖1-1
發現的問題:好像沒有變成“Doing”,並且拖動視窗的時候卡住不動了,3秒後突然變到想拖動到的位置,並且文本變成“Complete”?
【分析】GUI 程式在設計中要求所有的顯示變化都必須在主 GUI 線程中完成,如點擊事件和移動窗體。Windows 程式時通過 消息來實現,消息放入消息泵管理的消息隊列中。點擊按鈕時,按鈕的Click消息放入消息隊列。消息泵從隊列中移除該消息,並開始處理點擊事件的代碼,即 btnDo_Click 事件的代碼。btnDo_Click 事件會將觸發行為的消息放入隊列,但在 btnDo_Click 時間處理程式退出前(線程掛起 3 秒退出前),消息都無法執行。接著所有行為都發生了,但速度太快肉眼分辨不了。
圖1-2 點擊事件
圖1-3 點擊事件具體執行過程
現在我們加入 async/await 特性。
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 private async void btnDo_Click(object sender, EventArgs e) 9 { 10 btnDo.Enabled = false; 11 lblText.Text = @"Doing"; 12 13 await Task.Delay(3000); 14 15 btnDo.Enabled = true; 16 lblText.Text = @"Complete"; 17 } 18 }
圖1-4
現在,就是原先希望看到的效果。
【分析】btnDo_Click 事件處理程式先將前兩條消息壓入隊列,然後將自己從處理器移出,在3秒後(等待空閑任務完成後 Task.Delay )再將自己壓入隊列。這樣可以保持響應,並保證所有的消息可以線上程掛起的時間內被處理。
1.1 Task.Yield
Task.Yield 方法創建一個立刻返回的 awaitable。等待一個Yield可以讓非同步方法在執行後續部分的同時返回到調用方法。可以將其理解為 離開當前消息隊列,回到隊列末尾,讓 CPU 有時間處理其它任務。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 const int num = 1000000; 6 var t = DoStuff.Yield1000(num); 7 8 Loop(num / 10); 9 Loop(num / 10); 10 Loop(num / 10); 11 12 Console.WriteLine($"Sum: {t.Result}"); 13 14 Console.Read(); 15 } 16 17 /// <summary> 18 /// 迴圈 19 /// </summary> 20 /// <param name="num"></param> 21 private static void Loop(int num) 22 { 23 for (var i = 0; i < num; i++) ; 24 } 25 } 26 27 internal static class DoStuff 28 { 29 public static async Task<int> Yield1000(int n) 30 { 31 var sum = 0; 32 for (int i = 0; i < n; i++) 33 { 34 sum += i; 35 if (i % 1000 == 0) 36 { 37 await Task.Yield(); //創建非同步產生當前上下文的等待任務 38 } 39 } 40 41 return sum; 42 } 43 }
圖1.1-1
上述代碼每執行1000次迴圈就調用 Task.Yield 方法創建一個等待任務,讓處理器有時間處理其它任務。該方法在 GUI 程式中非常有用。
二、使用非同步 Lambda 表達式
將剛纔的視窗程式的點擊事件稍微改動一下。
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 7 //async (sender, e) 非同步表達式 8 btnDo.Click += async (sender, e) => 9 { 10 Do(false, "Doing"); 11 12 await Task.Delay(3000); 13 14 Do(true, "Finished"); 15 }; 16 } 17 18 private void Do(bool isEnable, string text) 19 { 20 btnDo.Enabled = isEnable; 21 lblText.Text = text; 22 } 23 }
還是原來的配方,還是熟悉的味道,還是原來哪個視窗,變的只是內涵。
圖2-1
三、完整的 GUI 程式
【原文地址】http://www.cnblogs.com/liqingwen/p/5877042.html
========預覽版,整理完畢後發佈到首頁=========