[C#] 走進非同步編程的世界 - 在 WinForm 中執行非同步操作

来源:http://www.cnblogs.com/liqingwen/archive/2016/09/20/5877042.html
-Advertisement-
Play Games

走進非同步編程的世界 - 在 WinForm 中執行非同步操作 序 這是繼《開始接觸 async/await 非同步編程》、《走進非同步編程的世界 - 剖析非同步方法》後的第三篇。主要介紹在 WinForm 中如何執行非同步操作。 目錄 在 WinForm 程式中執行非同步操作 在 WinForm 中使用非同步 L ...


走進非同步編程的世界 - 在 WinForm 中執行非同步操作

 

【鄙人】反骨仔  【原文地址】http://www.cnblogs.com/liqingwen/p/5877042.html 

   這是繼《開始接觸 async/await 非同步編程》、《走進非同步編程的世界 - 剖析非同步方法》後的第三篇。主要介紹在 WinForm 中如何執行非同步操作。

 

目錄

 

一、在 WinForm 程式中執行非同步操作

  下麵通過窗體示例演示以下操作-點擊按鈕後:

    ①將按鈕禁用,並將標簽內容改成:“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 秒退出前),消息都無法執行。(3 秒後)接著所有行為都發生了,但速度太快肉眼無法分辨才沒有發現標簽改成“Doing”。

圖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 程式中是比較有用的。

 

二、在 WinForm 中使用非同步 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

 

三、一個完整的 WinForm 程式

  現在在原來的基礎上添加了進度條,以及取消按鈕。

 1     public partial class Form1 : Form
 2     {
 3         private CancellationTokenSource _source;
 4         private CancellationToken _token;
 5 
 6         public Form1()
 7         {
 8             InitializeComponent();
 9         }
10 
11         /// <summary>
12         /// Do 按鈕事件
13         /// </summary>
14         /// <param name="sender"></param>
15         /// <param name="e"></param>
16         private async void btnDo_Click(object sender, EventArgs e)
17         {
18             btnDo.Enabled = false;
19 
20             _source = new CancellationTokenSource();
21             _token = _source.Token;
22 
23             var completedPercent = 0; //完成百分比
24             const int time = 10; //迴圈次數
25             const int timePercent = 100 / time; //進度條每次增加的進度值
26 
27             for (var i = 0; i < time; i++)
28             {
29                 if (_token.IsCancellationRequested)
30                 {
31                     break;
32                 }
33 
34                 try
35                 {
36                     await Task.Delay(500, _token);
37                     completedPercent = (i + 1) * timePercent;
38                 }
39                 catch (Exception)
40                 {
41                     completedPercent = i * timePercent;
42                 }
43                 finally
44                 {
45                     progressBar.Value = completedPercent;
46                 }
47             }
48 
49             var msg = _token.IsCancellationRequested ? $"進度為:{completedPercent}% 已被取消!" : $"已經完成";
50 
51             MessageBox.Show(msg, @"信息");
52 
53             progressBar.Value = 0;
54             InitTool();
55         }
56 
57         /// <summary>
58         /// 初始化窗體的工具控制項
59         /// </summary>
60         private void InitTool()
61         {
62             progressBar.Value = 0;
63             btnDo.Enabled = true;
64             btnCancel.Enabled = true;
65         }
66 
67         /// <summary>
68         /// 取消事件
69         /// </summary>
70         /// <param name="sender"></param>
71         /// <param name="e"></param>
72         private void btnCancel_Click(object sender, EventArgs e)
73         {
74             if (btnDo.Enabled) return;
75 
76             btnCancel.Enabled = false;
77             _source.Cancel();
78         }
79     }

 圖3-1

 

四、另一種非同步方式 - BackgroundWorker 類

  與 async/await 不同的是,你有時候可能需要一個額外的線程,在後臺持續完成某項任務,並不時與主線程通信,這時就需要用到 BackgroundWorker 類。主要用於 GUI 程式。

  書中的千言萬語不及一個簡單的示例。

 1     public partial class Form2 : Form
 2     {
 3         private readonly BackgroundWorker _worker = new BackgroundWorker();
 4 
 5         public Form2()
 6         {
 7             InitializeComponent();
 8 
 9             //設置 BackgroundWorker 屬性
10             _worker.WorkerReportsProgress = true;   //能否報告進度更新
11             _worker.WorkerSupportsCancellation = true;  //是否支持非同步取消
12 
13             //連接 BackgroundWorker 對象的處理程式
14             _worker.DoWork += _worker_DoWork;   //開始執行後臺操作時觸發,即調用 BackgroundWorker.RunWorkerAsync 時觸發
15             _worker.ProgressChanged += _worker_ProgressChanged; //調用 BackgroundWorker.ReportProgress(System.Int32) 時觸發
16             _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;   //當後臺操作已完成、被取消或引發異常時觸發
17         }
18 
19         /// <summary>
20         /// 當後臺操作已完成、被取消或引發異常時發生
21         /// </summary>
22         /// <param name="sender"></param>
23         /// <param name="e"></param>
24         private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
25         {
26             MessageBox.Show(e.Cancelled ? $@"進程已被取消:{progressBar.Value}%" : $@"進程執行完成:{progressBar.Value}%");
27             progressBar.Value = 0;
28         }
29 
30         /// <summary>
31         /// 調用 BackgroundWorker.ReportProgress(System.Int32) 時發生
32         /// </summary>
33         /// <param name="sender"></param>
34         /// <param name="e"></param>
35         private void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
36         {
37             progressBar.Value = e.ProgressPercentage;   //非同步任務的進度百分比
38         }
39 
40         /// <summary>
41         /// 開始執行後臺操作觸發,即調用 BackgroundWorker.RunWorkerAsync 時發生
42         /// </summary>
43         /// <param name="sender"></param>
44         /// <param name="e"></param>
45         private static void _worker_DoWork(object sender, DoWorkEventArgs e)
46         {
47             var worker = sender as BackgroundWorker;
48             if (worker == null)
49             {
50                 return;
51             }
52 
53             for (var i = 0; i < 10; i++)
54             {
55                 //判斷程式是否已請求取消後臺操作
56                 if (worker.CancellationPending)
57                 {
58                     e.Cancel = true;
59                     break;
60                 }
61 
62                 worker.ReportProgress((i + 1) * 10);    //觸發 BackgroundWorker.ProgressChanged 事件
63                 Thread.Sleep(250);  //線程掛起 250 毫秒
64             }
65         }
66 
67         private void btnDo_Click(object sender, EventArgs e)
68         {
69             //判斷 BackgroundWorker 是否正在執行非同步操作
70             if (!_worker.IsBusy)
71             {
72                 _worker.RunWorkerAsync();   //開始執行後臺操作
73             }
74         }
75 
76         private void btnCancel_Click(object sender, EventArgs e)
77         {
78             _worker.CancelAsync();  //請求取消掛起的後臺操作
79         }
80     }

圖4-1

 

 傳送門

  入門:《走進非同步編程的世界 - 開始接觸 async/await 非同步編程

  上篇:《走進非同步編程的世界 - 剖析非同步方法(上)》《走進非同步編程的世界 - 剖析非同步方法(下)

 

 


【參考】《Illustrated C# 2012》

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天開始為大家介紹下Linux中常用的命令,首先給大家介紹下Linux中使用頻率最高的命令--ls命令。 更多Linux命令詳情請看:Linux命令速查手冊 linux ls命令用於顯示指定工作目錄下之內容(列出目前工作目錄所含之文件及子目錄),還可以查看文件的許可權(包括目錄、文件夾、文件許可權),查 ...
  • 一:SDRAM SDRAM(Synchronous Dynamic Random Access Memory),同步動態隨機存儲器,同步是指 Memory工作需要同步時鐘,內部的命令的發送與數據的傳輸都以它為基準;動態是指需要不斷的刷新來保證數據不丟失;隨機是指數據不是線性依次存儲,而是自由指定地址 ...
  • 觀察情況一(字元串實例直接賦值給string類型的變數): 分析: 通過調試看出,兩個變數存儲的記憶體地址是一樣的,這個記憶體地址其實指向的是字元串常量區 圖解: 原理: 創建一個字元串對象,系統會先掃描常量區有沒有相同值的字元串,如果有,就直接返回常量區對應的地址 。 觀察情況二(通過 new 關鍵字 ...
  • 1、簡單控制項 Label - 文字,編譯後顯示的是<span> 一說到邊框:1、顏色 2、類型,比如solid實線3、width寬度Literal -裡面可以承載很多東西,比如文字,比如alert彈出視窗,傳遞JSt代碼 TextBox - 文本框 TextMode - 文本框類型,可以設置成pas ...
  • 很久在這裡寫博客。很多時候匹配紋理圖片和多邊形匹配,手工設置往往非常繁瑣,於是寫了一段從紋理圖片提取邊緣多邊形的代碼。但這份代碼只能提取“實心”的多邊形,並且只支持了一個多邊形。當然如果需要可以擴展使之能夠提取多個多邊形。基本思路如下: 1、快速填充紋理中被設為透明的部分。並獲得一個邊緣種子。 2、 ...
  • 1、簡單控制項 ①label 作用是顯示文字,編譯後的結果是span 邊框--邊框顏色,邊框樣式,邊框粗細 ②literal 作用是顯示文字,編譯後的結果不會生成任何元素,主要是用於傳遞JavaScript代碼 ③TextBox 文字輸入框 TextMode--SingleLine 被編譯為 type ...
  • 1.資料庫名為Demo,數據結構如圖 2.後臺代碼如下 ...
  • 1. 類和結構能夠實現介面 2. 介面聲明包含如下四種類型:屬性、方法、事件和索引;這些函數聲明不能包含任何實現代碼,而在每一個成員的主體後必須使用分號 3. 繼承介面的類或結構必須實現介面中的所有成員 4. 顯示介面的實現註意下麵的代碼 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...