Powered By Blogger

關於我自己

我的相片
網站經營斷斷續續,現在以分享程式練習為主。 因為工作需要,不時會有練習程式的需要。 所以將自己練習的過程分享給大家。 如果有幫助到各位,那就太好了! 如果針對本人或網站內容有任何問題, 歡迎與我聯絡。

2020年5月3日 星期日

【C# 語言】Task Wait and async await 練習

居裡貓前陣子因為某些使用上的需求,因此有了這次的練習。
雖然在這之前有幾次的使用上的經驗,但都是看著別人寫的程式碼,
依樣畫葫蘆, 並沒有更多的了解,所以當要重新使用的時候就無從下手。
那就讓我們看下去~

居裡貓前陣子碰到了 async/await 的使用需求,但卻沒辦法自己寫出一個範例或是說出個正確的寫作方法。
 因為在更之前有跟著別人所寫的程式碼模仿著寫,但並不了解這之間得來由。
經過幾天的 Google 後,找到了幾篇文章,看著文章的教學以及說明,
讓我有著打通任都二脈般的認知, 這裡先介紹我所瀏覽的文章:

ASP.NET async 基本心法 - 黑暗執行緒

C# 的 await 與 wait 的差異在哪裡 - C# .NET Blazor Research

※居裡貓在這個過程看了滿多文章資訊,只列出上面兩篇文章是居裡貓認為跟這次練習最有相關的文章。
※居裡貓以下內容並不會說過多的學術類的資訊,有需要的人請至上述網站閱讀。


現在簡單的說明為什麼會有這次的練習,以及接下來的練習內容大綱。
因為在一些寫程式的過程中有「非同步」、「同步」的問題,
在這些好像看過,但是卻跟他不怎麼熟識程式功能中開起來這次的練習。

居裡貓引用了上述第二篇文章的教學範例,重新撰寫了一個比較符合居裡貓自己的練習。
使用 C# 撰寫 WinForm 程式來了解「非同步」的使用。

非同步的簡單認知大概都是,同時有兩個人在工作之類的概念,
用在 WinForm 上面就像是,我有一個時間顯示跟迴圈運算等等功能同時運作,
並且!不會影響 Form 的其他操作!

簡述程式的執行流程:
程式開啟會啟動 Timer 執行時間的更新,並且紀錄程式開始執行的 Thread ID。
接著按鈕中的程式都會呼叫各自的 Mian 方法,也會紀錄 Thread ID。
Main 方法中會再呼叫 DoWait / DoAwait,而這個方法會包含 Before / Wait / After,
並且三個方法裡面都會紀錄 Thread ID。
重點的 Wait 方法會執行 Task.Run ,包含了 Thread.Sleep(7000)。
For loop 則會在 DoWait / DoAwait 之後執行,10次的 Thread.Sleep(500)。

那麼就開始看程式吧!


上圖就是本次練習的介面,
裡面包含一個即時顯示時間的 label,
一個使用 Task/Wait 方法的區域,一個使用 async/await 方法的區域。
三個 ListBox 顯示個別的時間、程式段落、Thread ID 資訊。
在中間 Task/Wait 區域中可以看到居裡貓標記的內容「This type will occur deadlock」,
表示這部份如果沒有特別的處理將會發生 deadlock 。
另外,還可以看到兩個區域都有一個 CheckBox ,Run For loop 的勾選功能,
這個功能將使程式而外執行 for loop 的行為。
※程式設計念來自於上述第二篇文章

接著我們來看一下執行中間區域會發生什麼事情。


我們可以看到中間顯示了程式執行的資訊,
但這之後視窗無法移動,甚至使用視窗截圖的按鍵也無法截圖(Alt+Prtscr),
看看工作管理員的狀態:

什麼他竟然不是出現「沒有回應」等等之類的資訊,但就是不能操作了!
這就是寫 WinForm 最討厭出現的情況,然而不管過了多久他就是不會恢復。
最後就只能結束工作才可以復原他!

讓我們接著看第二區域會呈現什麼樣的情況吧~



看到資訊上寫出不一樣的內容了嗎!?
而且再出現 「 wait 7 seconds 」這行訊息的時候視窗是可以拖曳移動的,
而且也能夠視窗截圖。
由此可見, async/await 的使用方法可以解決居裡貓使用上的問題。

那我們接著試試看勾選 Run For loop 會發生什麼事情吧!



居裡貓這次執行含有 For loop 的操作,
並且在開始寫出 For loop 的資訊時候按下視窗截圖,
但卻截下了 For loop 執行完畢後的視窗,
並且視窗不可拖曳移動。
看到這裡,眼尖的朋友應該也有注意 Thread ID 的資訊,
並且也會開始覺得為什麼 For loop 會有上述問題的原因,
甚至是中間區域 Task/Wait 會有問題的原因。

簡單的解釋,
因為 UI 會使用一個執行緒來執行所有工作,
一旦 UI 使用的執行緒發生繁忙的時候,
你我就休想操作視窗(UI),更別說其他的程式工作了,
非同步操作就是讓他可以有良好的分配工作執行緒使得 UI 不被卡住,
這也是為什麼居裡貓會另外練習的原因!
因為居裡貓參考的網站,那樣的作法在 Application 的程式中,並不會有這麼明顯的感受,
而且在 Task/Wait 也並不會發生那樣的問題。

底下將提供居裡貓本次的練習程式碼:


-------------------------------------------------------------------------------------------



  1. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
  2. ////    Reference By : [C# 的 await 與 wait 的差異在哪裡] https://csharpkh.blogspot.com/2019/04/CSharp-Await-Wait-Async-Thread-different.html   ////  
  3. ////    Description:                                                                                                                                                                           ////  
  4. ////    This practice is run in WinForm, so "DoWait" part result difference with reference                                                                                 ////  
  5. ////    Practice point is use Task replace Thread. When thread work done and get result(or respone) the Task provide some easy way to use.         ////  
  6. ////    But Task have some problem in WinForm used, like this practice "DoWait" part, will deadlock our program.                                                ////  
  7. ////    So, we need use Async/Await to solve the deadlock problem.                                                                                                           ////  
  8. ////    ReWrite by J.Y.L                                                                                                                                                                     ////  
  9. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
  10.   
  11. using System;  
  12. using System.Collections.Generic;  
  13. using System.ComponentModel;  
  14. using System.Data;  
  15. using System.Drawing;  
  16. using System.Linq;  
  17. using System.Text;  
  18. using System.Threading.Tasks;  
  19. using System.Windows.Forms;  
  20.   
  21. namespace Task_async_Wait_await_Practice  
  22. {  
  23.     public partial class Form1 : Form  
  24.     {  
  25.         public Form1()  
  26.         {  
  27.             InitializeComponent();  
  28.   
  29.             SetCallBack(lsb_Main, DateTime.Now.ToString() + " >> " + "Form1 running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  30.             //Console.WriteLine("Form1 running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  31.             StartTimer();  
  32.         }  
  33.  
  34.         #region DateTime   
  35.         private void tmr_DateTime_Tick(object sender, EventArgs e)  
  36.         {  
  37.             lblDateTime.Text = DateTime.Now.ToString();  
  38.         }  
  39.   
  40.         private void StartTimer()  
  41.         {  
  42.             SetCallBack(lsb_Main, DateTime.Now.ToString() + " >> " + "StartTimer running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  43.             //Console.WriteLine("StartTimer running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  44.             lblDateTime.Text = DateTime.Now.ToString();  
  45.             tmr_DateTime.Start();  
  46.         }  
  47.         #endregion // DateTime  
  48.  
  49.  
  50.         #region Task Wait  
  51.   
  52.         private void btnTaskWait_Click(object sender, EventArgs e)  
  53.         {  
  54.             Main_TaskWait();  
  55.         }  
  56.   
  57.         private void Main_TaskWait()  
  58.         {  
  59.             SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Main_TaskWait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  60.             SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Running Do Wait function");  
  61.             //Console.WriteLine("Main_TaskWait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  62.             //Console.WriteLine("Running Do Wait function");  
  63.   
  64.             DoWait();  
  65.   
  66.             if (ckbTaskWait.Checked == true)  
  67.             {  
  68.                 ForLoop(lsb_TaskWait, "Main_TaskWait");  
  69.             }              
  70.         }  
  71.   
  72.         private void DoWait()  
  73.         {  
  74.             Before_DoWait();  
  75.   
  76.             MyMethodAsync_DoWait().Wait(); // here is run task function  
  77.   
  78.             After_DoWait();  
  79.         }  
  80.   
  81.         private void Before_DoWait()  
  82.         {  
  83.             SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Before_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  84.             //Console.WriteLine("Before_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  85.         }  
  86.   
  87.         private void After_DoWait()  
  88.         {  
  89.             SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "After_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  90.             //Console.WriteLine("After_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  91.         }  
  92.   
  93.         private Task MyMethodAsync_DoWait()  
  94.         {  
  95.             SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Before run into MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  96.             //Console.WriteLine("Before run into MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  97.   
  98.             return Task.Run(() => {  
  99.                 SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Start running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  100.                 //Console.WriteLine("Start running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  101.   
  102.                 SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "MyMethodAsync_DoWait running wait 7 seconds");  
  103.                 //Console.WriteLine("MyMethodAsync_DoWait running wait 7 seconds");  
  104.                 System.Threading.Thread.Sleep(7000);  
  105.             });  
  106.         }  
  107.  
  108.         #endregion // Task Wait  
  109.  
  110.          
  111.         #region async await  
  112.   
  113.         private void btnAsyncAwait_Click(object sender, EventArgs e)  
  114.         {  
  115.             Main_AsyncAwait();  
  116.         }  
  117.   
  118.         private void Main_AsyncAwait()  
  119.         {  
  120.             SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Main_AsyncAwait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  121.             SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Running Do Wait function");  
  122.             //Console.WriteLine("Main_AsyncAwait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  123.             //Console.WriteLine("Running Do Wait function");  
  124.   
  125.             DoAwait();  
  126.   
  127.             if (ckbAsyncAwait.Checked == true)  
  128.             {  
  129.                 ForLoop(lsb_AsyncAwait, "Main_AsyncAwait");  
  130.             }              
  131.         }  
  132.   
  133.         private async void DoAwait()  
  134.         {  
  135.             Before_DoAwait();  
  136.   
  137.             await MyMethodAsync_DoAwait(); // here is run task function  
  138.   
  139.             After_DoAwait();  
  140.         }  
  141.   
  142.         private  void Before_DoAwait()  
  143.         {  
  144.             SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Before_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  145.             //Console.WriteLine("Before_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  146.         }  
  147.   
  148.         private void After_DoAwait()  
  149.         {  
  150.             SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "After_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  151.             //Console.WriteLine("After_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  152.         }  
  153.   
  154.         private Task MyMethodAsync_DoAwait()  
  155.         {  
  156.             SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Before run into MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  157.             //Console.WriteLine("Before run into MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  158.   
  159.             return Task.Run(() => {  
  160.                 SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Start running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  161.                 //Console.WriteLine("Start running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  162.   
  163.                 SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "MyMethodAsync_DoAwait running wait 7 seconds");  
  164.                 //Console.WriteLine("MyMethodAsync_DoAwait running wait 7 seconds");  
  165.                 System.Threading.Thread.Sleep(7000);  
  166.             });  
  167.         }  
  168.  
  169.         #endregion // async await  
  170.  
  171.  
  172.         #region Other  
  173.   
  174.         delegate void SetControlCallback(Control _ctrl, object _obj);  
  175.         private void SetCallBack(Control _ctrl, object _obj)  
  176.         {  
  177.             if (_ctrl is ListBox)  
  178.             {  
  179.                 if (((ListBox)_ctrl).InvokeRequired == true)  
  180.                 {  
  181.                     SetControlCallback _d = new SetControlCallback(SetCallBack);  
  182.                     this.Invoke(_d, new object[] { _ctrl, _obj });  
  183.                 }  
  184.                 else  
  185.                 {  
  186.                     ((ListBox)_ctrl).Items.Add(_obj);  
  187.                     ((ListBox)_ctrl).SelectedIndex = ((ListBox)_ctrl).Items.Count - 1;  
  188.                 }  
  189.             }  
  190.         }  
  191.   
  192.         private void ForLoop(ListBox _lsb, string _str)  
  193.         {  
  194.             for (int i = 0; i < 10; i++)  
  195.             {  
  196.                 System.Threading.Thread.Sleep(500);  
  197.                 SetCallBack(_lsb, DateTime.Now.ToString() + " >> " + _str + "running for loop index = " + i.ToString() + " , thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  198.                 //Console.WriteLine("Main_AsyncAwait running for loop index = " + i.ToString() + " , thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());  
  199.             }  
  200.         }  
  201.         #endregion // Other  
  202.     }  
  203. }  

-------------------------------------------------------------------------------------
重申,這次的練習都是經過參考所產生的,
相關資訊還請大家參考上面提供的兩個網站,
因此程式碼的設計並非重頭到尾皆由居裡貓本人所設計,
居裡貓只是將文章中的程式加以改寫成適合居裡貓理解後的樣子。
希望這次的介紹大家會喜歡,並且能夠幫助到大家。
同時感謝網路上各位先進們!
以上!



沒有留言:

張貼留言