Powered By Blogger

關於我自己

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

2020年11月29日 星期日

【VB.NET】Task.WaitAll 和 Task.WhenAll 應用

居裡貓這幾天剛好有一些想法,假設某些工作流程可以不需要先等待其完成,則往下繼續進行,但在某個時機點還是要取得其工作結果,聽請來很饒舌,總之,因此研究了一下這次要分享的文章內容,讓我們看下去吧!

前言饒舌的說明,讓居裡貓畫個流程圖來解說一下,預設的問題狀況大概是如此。


上圖為例,流程C是可以不需要先等待完成就可以先往下流程進行,
但在流程結束的時候還是要等待訂確認流程C所做的工作是否都完成了。

好了,說明完問題的原因,那就來說明大概怎麼樣的方法來處理這個行為。
首先當然是要把流程C的工作利用一個執行序來來處理,
而且要把這個執行序做成非同步作業,
然後非同步作業最麻煩的就是確認作業是否完成,
因為完成了我們才有辦法知道工作結果,
所以,就進入這次練習的主題了!

Task.WaitAll 和 Task.WhenAll 都提供等待陣列形式的 Task 工作者的狀態。
請看微軟的 Docs WaitAll(Task[]) 和 WhenAll(Task[])
那接下來就讓我們看看這次的範例程式吧!!




上圖的主介面說明,
1. Timer時間,檢視UI狀態。
2. Work Times,設定要執行多少次工作流程。
3. Run Work Wait All,執行 Task.WaitAll 的範例。
4. Run Work When All ,執行 Task.WhanAll 的範例。
5. Log,顯示流程動作狀態。

說明一下測試的工作內容做了什麼樣的行為,
工作內容(WorkHard)是以For loop來累加數值,
For loop的次數以Random隨機1~10的數次*1000為例,
完成累加後利用Threading.Thread.Sleep(1000)等待一秒的時間才結束。
工作次數則以介面上的Work Times來決定(Work Times + 1次)。
並且設計一個類別,內容有兩個主要的行為,
一個是Task的變數,目的為針對某次的工作流程。
一個是result的變數,目的為取得某次的工作流程結果。
然後這個工作次數會以List的方式來使用。




上圖的行為說明就是,總工作次數Work Times : 1,所以是 1+1次。
橘色的框為Task.WaitAll的範例結果;藍色的框為Task.WhenAll的範例結果。
Log中顯示的資訊有進入WaitAll Function或WhenAll Function;
New TaskWork、進入WorkHard工作內容、離開WorkHard工作內容、
離開WaitAll Function或WhenAll Function。

因為,本次範例有執行顯示Log的行為,
所以不管是WaitAll或WhenAll都必須要考慮,
UI DeadLock的問題!

WaitAll有一個多載可以等待某個ms時間來結束等待行為,
可以讓使用者決定我要等多久我就不等了,
要在WaitAll中解決UI DeadLock,就必須設定這個時間,
並且加上Application.DoEvent的方法解決UI DeadLock的問題。
如果不解決程式就卡死,但如果不好好等待結束,就會取不到完整的所有結果。

WhenAll則沒有等待時間的設定,
所以在呼叫WhenAll就一定會等待所有工作結束才會離開,
在解決UI DeadLock的方法則可以使用async/await的作法來解決問題。

在取得結果的方法,
都是利用Task.Result的方式來取得結果,
特別說明一下,
WhealAll可以直接在呼叫的時候取得Task工作結果,
但如果使用await的時候則會得到回傳結果,
所以我這裡統一使用Task.Result來取得結果。
因此在等待後會有一個For loop來取得Result!

說了這麼多,直接來看看程式碼吧!
本次範例的介面設計皆由程式碼產生,
所以以下程式碼中看會看到介面設計的方法內容,請見諒。


---------------------------程式碼分割線-----------------------------------------------------------

  1. Imports System.Threading.Tasks  
  2.   
  3. Public Class Form1  
  4. #Region "Fields"  
  5.   
  6.     Private _btnTestUI As Button  
  7.     Private _lblTimer As Label  
  8.     Private _btnRun() As Button  
  9.     Private _tmr As System.Windows.Forms.Timer  
  10.     Private _lsWork As List(Of TestTaskClass)  
  11.     Private _lblWorkTimes As Label  
  12.     Private _nudWorkTimes As NumericUpDown  
  13.     Private _rnd As Random  
  14.     Private _lblLog As Label  
  15.     Private _lsbLog As ListBox  
  16.   
  17.     Private Delegate Sub _delsetControlCallBack(ByVal _crl As Control, ByVal obj As Object)  
  18.  
  19. #End Region  
  20.  
  21. #Region "Construct"  
  22.   
  23.     Sub New()  
  24.   
  25.         ' 設計工具需要此呼叫。  
  26.         InitializeComponent()  
  27.   
  28.         ' 在 InitializeComponent() 呼叫之後加入所有初始設定。  
  29.   
  30.         InitializeControl()  
  31.   
  32.     End Sub  
  33.  
  34. #End Region  
  35.  
  36. #Region "Event"  
  37.   
  38.     Private Sub btnTestUI_Click(ByVal sender As ObjectByVal e As EventArgs)  
  39.         Me.Controls.Clear()  
  40.   
  41.         InitializeControl()  
  42.     End Sub  
  43.   
  44.     Private Sub tmr_Tick(ByVal sender As ObjectByVal e As EventArgs)  
  45.         '_lblTimer.Text = Now.ToString("yyyy/MM/dd HH:mm:ss")  
  46.         SetControlCallBackFunction(_lblTimer, Now.ToString("yyyy/MM/dd HH:mm:ss"))  
  47.     End Sub  
  48.   
  49.     Private Async Sub btnRun_Click(ByVal sender As ObjectByVal e As EventArgs)  
  50.         Dim _tmpButton As Button = CType(sender, Button)  
  51.         Dim _tmpLen As Integer  
  52.         Dim _str As String  
  53.         _tmpLen = Convert.ToInt32(_nudWorkTimes.Value)  
  54.         If _lsWork.Count > 0 Then  
  55.             _lsWork.Clear()  
  56.         End If  
  57.   
  58.         Select Case _tmpButton.Name  
  59.             Case "btnRunWorkWaitAll"  
  60.                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WaitAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()  
  61.                 SetControlCallBackFunction(_lsbLog, _str)  
  62.   
  63.                 For _index As Integer = 0 To _tmpLen  
  64.                     _lsWork.Add(New TestTaskClass)  
  65.                     _lsWork.Item(_index).taskWorker = New Task(Of Double())(Function()  
  66.                                                                                 Dim _ret As Double()  
  67.                                                                                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") &  
  68.                                                                                 " >> Now New Task WaitAll. The ManagedThreadID: " _  
  69.                                                                                 & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &  
  70.                                                                                 " The Current Task ID: " & Task.CurrentId.ToString()  
  71.                                                                                 SetControlCallBackFunction(_lsbLog, _str)  
  72.                                                                                 'Console.WriteLine(_str)  
  73.   
  74.                                                                                 _ret = WorkHard(Convert.ToDouble(_rnd.Next(1, 10)))  
  75.                                                                                 Return _ret  
  76.                                                                             End Function)  
  77.                     _lsWork.Item(_index).taskWorker.Start()  
  78.                 Next  
  79.   
  80.                 '' Here need use doevent to fix ui deadlock  
  81.                 'Task.WaitAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)  
  82.                 While Task.WaitAll(_lsWork.Select(Function(x) x.taskWorker).ToArray, 10) <> True  
  83.                     Application.DoEvents()  
  84.                 End While  
  85.   
  86.                 For _index2 As Integer = 0 To _lsWork.Count - 1  
  87.                     _lsWork.Item(_index2).threadID = _lsWork.Item(_index2).taskWorker.Result(0)  
  88.                     _lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result(1)  
  89.                     '_lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result  
  90.                 Next  
  91.   
  92.                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WaitAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()  
  93.                 SetControlCallBackFunction(_lsbLog, _str)  
  94.             Case "btnRunWorkWhenAll"  
  95.                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WhenAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()  
  96.                 SetControlCallBackFunction(_lsbLog, _str)  
  97.   
  98.                 For _index As Integer = 0 To _tmpLen  
  99.                     _lsWork.Add(New TestTaskClass)  
  100.                     _lsWork.Item(_index).taskWorker = New Task(Of Double())(Function()  
  101.                                                                                 Dim _ret As Double()  
  102.                                                                                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") &  
  103.                                                                                 " >> Now new Task WhenAll. The ManagedThreadID: " _  
  104.                                                                                 & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &  
  105.                                                                                 " The Current Task ID: " & Task.CurrentId.ToString()  
  106.                                                                                 SetControlCallBackFunction(_lsbLog, _str)  
  107.                                                                                 'Console.WriteLine(_str)  
  108.   
  109.                                                                                 _ret = WorkHard(Convert.ToDouble(_rnd.Next(1, 10)))  
  110.                                                                                 Return _ret  
  111.                                                                             End Function)  
  112.   
  113.   
  114.                     _lsWork.Item(_index).taskWorker.Start()  
  115.                 Next  
  116.   
  117.                 '' Here need use awati to fix ui deadlock  
  118.                 ''Dim dans As Task  
  119.                 ''Dim ans  
  120.                 ''dans = Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)  
  121.                 ''ans = Await Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)  
  122.                 Await Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)  
  123.   
  124.   
  125.                 For _index2 As Integer = 0 To _lsWork.Count - 1  
  126.                     _lsWork.Item(_index2).threadID = _lsWork.Item(_index2).taskWorker.Result(0)  
  127.                     _lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result(1)  
  128.                     '_lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result  
  129.                 Next  
  130.   
  131.                 _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WhenAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()  
  132.                 SetControlCallBackFunction(_lsbLog, _str)  
  133.         End Select  
  134.     End Sub  
  135.  
  136.  
  137. #End Region  
  138.  
  139. #Region "Functions"  
  140.   
  141.     Private Sub InitializeControl()  
  142.   
  143.         Me.Size = New Size(750, 550)  
  144.         Me.Text = "Task WaitAll and Whenall Practice"  
  145.   
  146.         'CreateButton_TestUI()  
  147.         CreateLabel_Timer()  
  148.         CreateButton_Run()  
  149.         CreateLabel_WorkTimes()  
  150.         CreateNumericUpDown()  
  151.         CreateLabel_Log()  
  152.         CreateListBox_Log()  
  153.   
  154.         _tmr = New Timer  
  155.         _tmr.Interval = 1000  
  156.         AddHandler _tmr.Tick, AddressOf tmr_Tick  
  157.         _tmr.Start()  
  158.   
  159.         _lsWork = New List(Of TestTaskClass)  
  160.   
  161.         _rnd = New Random()  
  162.   
  163.     End Sub  
  164.   
  165.     Private Function WorkHard(ByVal _count As Integer)  
  166.         Dim _str As String  
  167.         'Dim _ret As Double  
  168.         Dim _ret(1) As Double  
  169.         Dim _len As Integer  
  170.         _len = _count * 1000  
  171.   
  172.         _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WrokHard Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &  
  173.             " Total length: " & _len.ToString()  
  174.         SetControlCallBackFunction(_lsbLog, _str)  
  175.         'Console.WriteLine(_str)  
  176.   
  177.         _ret(0) = Threading.Thread.CurrentThread.ManagedThreadId  
  178.         For i As Integer = 0 To _len  
  179.             '_ret += i  
  180.             _ret(1) += i  
  181.   
  182.             'System.Threading.Thread.Sleep(1)  
  183.         Next  
  184.         System.Threading.Thread.Sleep(1000)  
  185.   
  186.         _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WrokHard Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &  
  187.             " The _ret: " & _ret(1)  
  188.         SetControlCallBackFunction(_lsbLog, _str)  
  189.         'Console.WriteLine(_str)  
  190.   
  191.         Return _ret  
  192.     End Function  
  193.   
  194.     Private Sub SetControlCallBackFunction(ByVal _ctrl As Control, ByVal _obj As Object)  
  195.         If TypeOf (_ctrl) Is ListBox Then  
  196.             Dim _tmpLsb As ListBox = CType(_ctrl, ListBox)  
  197.             Select Case _tmpLsb.Name  
  198.                 Case "lsbLog"  
  199.                     If _tmpLsb.InvokeRequired = True Then  
  200.                         Dim d As _delsetControlCallBack = New _delsetControlCallBack(AddressOf SetControlCallBackFunction)  
  201.                         _tmpLsb.Invoke(d, New Object() {_tmpLsb, _obj})  
  202.                     Else  
  203.                         _tmpLsb.Items.Add(_obj)  
  204.                         _tmpLsb.SelectedIndex = _tmpLsb.Items.Count - 1  
  205.                     End If  
  206.                 Case Else  
  207.             End Select  
  208.         ElseIf TypeOf (_ctrl) Is Label Then  
  209.             Dim _tmplbl As Label = CType(_ctrl, Label)  
  210.             Select Case _tmplbl.Name  
  211.                 Case "lblTimer"  
  212.                     If _tmplbl.InvokeRequired = True Then  
  213.                         Dim d As _delsetControlCallBack = New _delsetControlCallBack(AddressOf SetControlCallBackFunction)  
  214.                         _tmplbl.Invoke(d, New Object() {_ctrl, _obj})  
  215.                     Else  
  216.                         _tmplbl.Text = Convert.ToString(_obj)  
  217.                     End If  
  218.                 Case Else  
  219.             End Select  
  220.         Else  
  221.   
  222.         End If  
  223.     End Sub  
  224.  
  225. #Region "Create Control Function"  
  226.   
  227.     Private Sub CreateButton_TestUI()  
  228.         _btnTestUI = New Button  
  229.         _btnTestUI.Name = "btnTestUI"  
  230.         _btnTestUI.Text = "Test UI"  
  231.         _btnTestUI.Location = New Point(Me.Width - 100, Me.Height - 100)  
  232.         AddHandler _btnTestUI.Click, AddressOf btnTestUI_Click  
  233.   
  234.         Me.Controls.Add(_btnTestUI)  
  235.     End Sub  
  236.   
  237.     Private Sub CreateLabel_Timer()  
  238.         _lblTimer = New Label  
  239.         _lblTimer.Name = "lblTimer"  
  240.         _lblTimer.Text = Now.ToString("yyyy/MM/dd HH:mm:ss")  
  241.         _lblTimer.Location = New Point(10, 10)  
  242.         _lblTimer.Size = New Size(150, 20)  
  243.   
  244.         Me.Controls.Add(_lblTimer)  
  245.     End Sub  
  246.   
  247.     Private Sub CreateButton_Run(Optional ByVal _count As Integer = 1)  
  248.         ReDim _btnRun(_count)  
  249.   
  250.         For i As Integer = 0 To _btnRun.Length - 1  
  251.             _btnRun(i) = New Button  
  252.   
  253.             Select Case i  
  254.                 Case 0  
  255.                     _btnRun(i).Name = "btnRunWorkWaitAll"  
  256.                     _btnRun(i).Text = "Run Work Wait All"  
  257.                     _btnRun(i).Location = New Point(10, 70)  
  258.                     _btnRun(i).Size = New Size(150, 20)  
  259.                 Case 1  
  260.                     _btnRun(i).Name = "btnRunWorkWhenAll"  
  261.                     _btnRun(i).Text = "Run Work When All"  
  262.                     _btnRun(i).Location = New Point(10, 100)  
  263.                     _btnRun(i).Size = New Size(150, 20)  
  264.             End Select  
  265.             AddHandler _btnRun(i).Click, AddressOf btnRun_Click  
  266.   
  267.             Me.Controls.Add(_btnRun(i))  
  268.         Next  
  269.   
  270.     End Sub  
  271.   
  272.     Private Sub CreateLabel_WorkTimes()  
  273.         _lblWorkTimes = New Label  
  274.         _lblWorkTimes.Name = "lblWorkTimes"  
  275.         _lblWorkTimes.Text = "Work Times:"  
  276.         _lblWorkTimes.Location = New Point(10, 40)  
  277.         _lblWorkTimes.Size = New Size(100, 20)  
  278.   
  279.         Me.Controls.Add(_lblWorkTimes)  
  280.     End Sub  
  281.   
  282.     Private Sub CreateNumericUpDown()  
  283.         _nudWorkTimes = New NumericUpDown  
  284.         _nudWorkTimes.Name = "nudWorkTimes"  
  285.         _nudWorkTimes.Value = Convert.ToDecimal(1)  
  286.         _nudWorkTimes.Maximum = 100  
  287.         _nudWorkTimes.Minimum = 1  
  288.         _nudWorkTimes.Increment = 1  
  289.         _nudWorkTimes.Location = New Point(110, 40)  
  290.         _nudWorkTimes.Size = New Size(50, 20)  
  291.   
  292.         Me.Controls.Add(_nudWorkTimes)  
  293.     End Sub  
  294.   
  295.     Private Sub CreateLabel_Log()  
  296.         _lblLog = New Label  
  297.         _lblLog.Name = "lblLog"  
  298.         _lblLog.Text = "Log:"  
  299.         _lblLog.Location = New Point(10, 130)  
  300.         _lblLog.Size = New Size(50, 20)  
  301.   
  302.         Me.Controls.Add(_lblLog)  
  303.     End Sub  
  304.   
  305.     Private Sub CreateListBox_Log()  
  306.         _lsbLog = New ListBox  
  307.         _lsbLog.Name = "lsbLog"  
  308.         _lsbLog.Location = New Point(10, 150)  
  309.         _lsbLog.Size = New Size(600, 300)  
  310.         _lsbLog.ScrollAlwaysVisible = True  
  311.         _lsbLog.HorizontalScrollbar = True  
  312.   
  313.         Me.Controls.Add(_lsbLog)  
  314.     End Sub  
  315. #End Region  
  316.  
  317. #End Region  
  318.   
  319. End Class  
  320.   
  321. Public Class TestTaskClass  
  322.   
  323.     'Public taskWorker As Task(Of Double)  
  324.     Public taskWorker As Task(Of Double())  
  325.     Public threadID As Integer  
  326.     Public result As Double  
  327.   
  328.     Sub New()  
  329.   
  330.     End Sub  
  331.   
  332. End Class  



沒有留言:

張貼留言