雖然在這之前有幾次的使用上的經驗,但都是看著別人寫的程式碼,
依樣畫葫蘆, 並沒有更多的了解,所以當要重新使用的時候就無從下手。
那就讓我們看下去~
居裡貓前陣子碰到了 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 也並不會發生那樣的問題。
底下將提供居裡貓本次的練習程式碼:
-------------------------------------------------------------------------------------------
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //// Reference By : [C# 的 await 與 wait 的差異在哪裡] https://csharpkh.blogspot.com/2019/04/CSharp-Await-Wait-Async-Thread-different.html ////
- //// Description: ////
- //// This practice is run in WinForm, so "DoWait" part result difference with reference ////
- //// Practice point is use Task replace Thread. When thread work done and get result(or respone) the Task provide some easy way to use. ////
- //// But Task have some problem in WinForm used, like this practice "DoWait" part, will deadlock our program. ////
- //// So, we need use Async/Await to solve the deadlock problem. ////
- //// ReWrite by J.Y.L ////
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- namespace Task_async_Wait_await_Practice
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- SetCallBack(lsb_Main, DateTime.Now.ToString() + " >> " + "Form1 running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Form1 running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- StartTimer();
- }
- #region DateTime
- private void tmr_DateTime_Tick(object sender, EventArgs e)
- {
- lblDateTime.Text = DateTime.Now.ToString();
- }
- private void StartTimer()
- {
- SetCallBack(lsb_Main, DateTime.Now.ToString() + " >> " + "StartTimer running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("StartTimer running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- lblDateTime.Text = DateTime.Now.ToString();
- tmr_DateTime.Start();
- }
- #endregion // DateTime
- #region Task Wait
- private void btnTaskWait_Click(object sender, EventArgs e)
- {
- Main_TaskWait();
- }
- private void Main_TaskWait()
- {
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Main_TaskWait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Running Do Wait function");
- //Console.WriteLine("Main_TaskWait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Running Do Wait function");
- DoWait();
- if (ckbTaskWait.Checked == true)
- {
- ForLoop(lsb_TaskWait, "Main_TaskWait");
- }
- }
- private void DoWait()
- {
- Before_DoWait();
- MyMethodAsync_DoWait().Wait(); // here is run task function
- After_DoWait();
- }
- private void Before_DoWait()
- {
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Before_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Before_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- }
- private void After_DoWait()
- {
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "After_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("After_DoWait running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- }
- private Task MyMethodAsync_DoWait()
- {
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Before run into MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Before run into MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- return Task.Run(() => {
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "Start running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Start running MyMethodAsync_DoWait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- SetCallBack(lsb_TaskWait, DateTime.Now.ToString() + " >> " + "MyMethodAsync_DoWait running wait 7 seconds");
- //Console.WriteLine("MyMethodAsync_DoWait running wait 7 seconds");
- System.Threading.Thread.Sleep(7000);
- });
- }
- #endregion // Task Wait
- #region async await
- private void btnAsyncAwait_Click(object sender, EventArgs e)
- {
- Main_AsyncAwait();
- }
- private void Main_AsyncAwait()
- {
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Main_AsyncAwait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Running Do Wait function");
- //Console.WriteLine("Main_AsyncAwait running, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Running Do Wait function");
- DoAwait();
- if (ckbAsyncAwait.Checked == true)
- {
- ForLoop(lsb_AsyncAwait, "Main_AsyncAwait");
- }
- }
- private async void DoAwait()
- {
- Before_DoAwait();
- await MyMethodAsync_DoAwait(); // here is run task function
- After_DoAwait();
- }
- private void Before_DoAwait()
- {
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Before_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Before_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- }
- private void After_DoAwait()
- {
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "After_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("After_DoAwait running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- }
- private Task MyMethodAsync_DoAwait()
- {
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Before run into MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Before run into MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- return Task.Run(() => {
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "Start running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Start running MyMethodAsync_DoAwait, thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- SetCallBack(lsb_AsyncAwait, DateTime.Now.ToString() + " >> " + "MyMethodAsync_DoAwait running wait 7 seconds");
- //Console.WriteLine("MyMethodAsync_DoAwait running wait 7 seconds");
- System.Threading.Thread.Sleep(7000);
- });
- }
- #endregion // async await
- #region Other
- delegate void SetControlCallback(Control _ctrl, object _obj);
- private void SetCallBack(Control _ctrl, object _obj)
- {
- if (_ctrl is ListBox)
- {
- if (((ListBox)_ctrl).InvokeRequired == true)
- {
- SetControlCallback _d = new SetControlCallback(SetCallBack);
- this.Invoke(_d, new object[] { _ctrl, _obj });
- }
- else
- {
- ((ListBox)_ctrl).Items.Add(_obj);
- ((ListBox)_ctrl).SelectedIndex = ((ListBox)_ctrl).Items.Count - 1;
- }
- }
- }
- private void ForLoop(ListBox _lsb, string _str)
- {
- for (int i = 0; i < 10; i++)
- {
- System.Threading.Thread.Sleep(500);
- SetCallBack(_lsb, DateTime.Now.ToString() + " >> " + _str + "running for loop index = " + i.ToString() + " , thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- //Console.WriteLine("Main_AsyncAwait running for loop index = " + i.ToString() + " , thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
- }
- }
- #endregion // Other
- }
- }
-------------------------------------------------------------------------------------
重申,這次的練習都是經過參考所產生的,相關資訊還請大家參考上面提供的兩個網站,因此程式碼的設計並非重頭到尾皆由居裡貓本人所設計,居裡貓只是將文章中的程式加以改寫成適合居裡貓理解後的樣子。希望這次的介紹大家會喜歡,並且能夠幫助到大家。同時感謝網路上各位先進們!以上!
沒有留言:
張貼留言