前言饒舌的說明,讓居裡貓畫個流程圖來解說一下,預設的問題狀況大概是如此。
上圖為例,流程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!
說了這麼多,直接來看看程式碼吧!
本次範例的介面設計皆由程式碼產生,
本次範例的介面設計皆由程式碼產生,
所以以下程式碼中看會看到介面設計的方法內容,請見諒。
---------------------------程式碼分割線-----------------------------------------------------------
- Imports System.Threading.Tasks
- Public Class Form1
- #Region "Fields"
- Private _btnTestUI As Button
- Private _lblTimer As Label
- Private _btnRun() As Button
- Private _tmr As System.Windows.Forms.Timer
- Private _lsWork As List(Of TestTaskClass)
- Private _lblWorkTimes As Label
- Private _nudWorkTimes As NumericUpDown
- Private _rnd As Random
- Private _lblLog As Label
- Private _lsbLog As ListBox
- Private Delegate Sub _delsetControlCallBack(ByVal _crl As Control, ByVal obj As Object)
- #End Region
- #Region "Construct"
- Sub New()
- ' 設計工具需要此呼叫。
- InitializeComponent()
- ' 在 InitializeComponent() 呼叫之後加入所有初始設定。
- InitializeControl()
- End Sub
- #End Region
- #Region "Event"
- Private Sub btnTestUI_Click(ByVal sender As Object, ByVal e As EventArgs)
- Me.Controls.Clear()
- InitializeControl()
- End Sub
- Private Sub tmr_Tick(ByVal sender As Object, ByVal e As EventArgs)
- '_lblTimer.Text = Now.ToString("yyyy/MM/dd HH:mm:ss")
- SetControlCallBackFunction(_lblTimer, Now.ToString("yyyy/MM/dd HH:mm:ss"))
- End Sub
- Private Async Sub btnRun_Click(ByVal sender As Object, ByVal e As EventArgs)
- Dim _tmpButton As Button = CType(sender, Button)
- Dim _tmpLen As Integer
- Dim _str As String
- _tmpLen = Convert.ToInt32(_nudWorkTimes.Value)
- If _lsWork.Count > 0 Then
- _lsWork.Clear()
- End If
- Select Case _tmpButton.Name
- Case "btnRunWorkWaitAll"
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WaitAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- For _index As Integer = 0 To _tmpLen
- _lsWork.Add(New TestTaskClass)
- _lsWork.Item(_index).taskWorker = New Task(Of Double())(Function()
- Dim _ret As Double()
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") &
- " >> Now New Task WaitAll. The ManagedThreadID: " _
- & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &
- " The Current Task ID: " & Task.CurrentId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- 'Console.WriteLine(_str)
- _ret = WorkHard(Convert.ToDouble(_rnd.Next(1, 10)))
- Return _ret
- End Function)
- _lsWork.Item(_index).taskWorker.Start()
- Next
- '' Here need use doevent to fix ui deadlock
- 'Task.WaitAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)
- While Task.WaitAll(_lsWork.Select(Function(x) x.taskWorker).ToArray, 10) <> True
- Application.DoEvents()
- End While
- For _index2 As Integer = 0 To _lsWork.Count - 1
- _lsWork.Item(_index2).threadID = _lsWork.Item(_index2).taskWorker.Result(0)
- _lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result(1)
- '_lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result
- Next
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WaitAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- Case "btnRunWorkWhenAll"
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WhenAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- For _index As Integer = 0 To _tmpLen
- _lsWork.Add(New TestTaskClass)
- _lsWork.Item(_index).taskWorker = New Task(Of Double())(Function()
- Dim _ret As Double()
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") &
- " >> Now new Task WhenAll. The ManagedThreadID: " _
- & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &
- " The Current Task ID: " & Task.CurrentId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- 'Console.WriteLine(_str)
- _ret = WorkHard(Convert.ToDouble(_rnd.Next(1, 10)))
- Return _ret
- End Function)
- _lsWork.Item(_index).taskWorker.Start()
- Next
- '' Here need use awati to fix ui deadlock
- ''Dim dans As Task
- ''Dim ans
- ''dans = Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)
- ''ans = Await Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)
- Await Task.WhenAll(_lsWork.Select(Function(x) x.taskWorker).ToArray)
- For _index2 As Integer = 0 To _lsWork.Count - 1
- _lsWork.Item(_index2).threadID = _lsWork.Item(_index2).taskWorker.Result(0)
- _lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result(1)
- '_lsWork.Item(_index2).result = _lsWork.Item(_index2).taskWorker.Result
- Next
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WhenAll Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- End Select
- End Sub
- #End Region
- #Region "Functions"
- Private Sub InitializeControl()
- Me.Size = New Size(750, 550)
- Me.Text = "Task WaitAll and Whenall Practice"
- 'CreateButton_TestUI()
- CreateLabel_Timer()
- CreateButton_Run()
- CreateLabel_WorkTimes()
- CreateNumericUpDown()
- CreateLabel_Log()
- CreateListBox_Log()
- _tmr = New Timer
- _tmr.Interval = 1000
- AddHandler _tmr.Tick, AddressOf tmr_Tick
- _tmr.Start()
- _lsWork = New List(Of TestTaskClass)
- _rnd = New Random()
- End Sub
- Private Function WorkHard(ByVal _count As Integer)
- Dim _str As String
- 'Dim _ret As Double
- Dim _ret(1) As Double
- Dim _len As Integer
- _len = _count * 1000
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now into WrokHard Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &
- " Total length: " & _len.ToString()
- SetControlCallBackFunction(_lsbLog, _str)
- 'Console.WriteLine(_str)
- _ret(0) = Threading.Thread.CurrentThread.ManagedThreadId
- For i As Integer = 0 To _len
- '_ret += i
- _ret(1) += i
- 'System.Threading.Thread.Sleep(1)
- Next
- System.Threading.Thread.Sleep(1000)
- _str = Now.ToString("yyyy/MM/dd HH:mm:ss") & " >> Now out WrokHard Function. The ManagedThreadID: " & Threading.Thread.CurrentThread.ManagedThreadId.ToString() &
- " The _ret: " & _ret(1)
- SetControlCallBackFunction(_lsbLog, _str)
- 'Console.WriteLine(_str)
- Return _ret
- End Function
- Private Sub SetControlCallBackFunction(ByVal _ctrl As Control, ByVal _obj As Object)
- If TypeOf (_ctrl) Is ListBox Then
- Dim _tmpLsb As ListBox = CType(_ctrl, ListBox)
- Select Case _tmpLsb.Name
- Case "lsbLog"
- If _tmpLsb.InvokeRequired = True Then
- Dim d As _delsetControlCallBack = New _delsetControlCallBack(AddressOf SetControlCallBackFunction)
- _tmpLsb.Invoke(d, New Object() {_tmpLsb, _obj})
- Else
- _tmpLsb.Items.Add(_obj)
- _tmpLsb.SelectedIndex = _tmpLsb.Items.Count - 1
- End If
- Case Else
- End Select
- ElseIf TypeOf (_ctrl) Is Label Then
- Dim _tmplbl As Label = CType(_ctrl, Label)
- Select Case _tmplbl.Name
- Case "lblTimer"
- If _tmplbl.InvokeRequired = True Then
- Dim d As _delsetControlCallBack = New _delsetControlCallBack(AddressOf SetControlCallBackFunction)
- _tmplbl.Invoke(d, New Object() {_ctrl, _obj})
- Else
- _tmplbl.Text = Convert.ToString(_obj)
- End If
- Case Else
- End Select
- Else
- End If
- End Sub
- #Region "Create Control Function"
- Private Sub CreateButton_TestUI()
- _btnTestUI = New Button
- _btnTestUI.Name = "btnTestUI"
- _btnTestUI.Text = "Test UI"
- _btnTestUI.Location = New Point(Me.Width - 100, Me.Height - 100)
- AddHandler _btnTestUI.Click, AddressOf btnTestUI_Click
- Me.Controls.Add(_btnTestUI)
- End Sub
- Private Sub CreateLabel_Timer()
- _lblTimer = New Label
- _lblTimer.Name = "lblTimer"
- _lblTimer.Text = Now.ToString("yyyy/MM/dd HH:mm:ss")
- _lblTimer.Location = New Point(10, 10)
- _lblTimer.Size = New Size(150, 20)
- Me.Controls.Add(_lblTimer)
- End Sub
- Private Sub CreateButton_Run(Optional ByVal _count As Integer = 1)
- ReDim _btnRun(_count)
- For i As Integer = 0 To _btnRun.Length - 1
- _btnRun(i) = New Button
- Select Case i
- Case 0
- _btnRun(i).Name = "btnRunWorkWaitAll"
- _btnRun(i).Text = "Run Work Wait All"
- _btnRun(i).Location = New Point(10, 70)
- _btnRun(i).Size = New Size(150, 20)
- Case 1
- _btnRun(i).Name = "btnRunWorkWhenAll"
- _btnRun(i).Text = "Run Work When All"
- _btnRun(i).Location = New Point(10, 100)
- _btnRun(i).Size = New Size(150, 20)
- End Select
- AddHandler _btnRun(i).Click, AddressOf btnRun_Click
- Me.Controls.Add(_btnRun(i))
- Next
- End Sub
- Private Sub CreateLabel_WorkTimes()
- _lblWorkTimes = New Label
- _lblWorkTimes.Name = "lblWorkTimes"
- _lblWorkTimes.Text = "Work Times:"
- _lblWorkTimes.Location = New Point(10, 40)
- _lblWorkTimes.Size = New Size(100, 20)
- Me.Controls.Add(_lblWorkTimes)
- End Sub
- Private Sub CreateNumericUpDown()
- _nudWorkTimes = New NumericUpDown
- _nudWorkTimes.Name = "nudWorkTimes"
- _nudWorkTimes.Value = Convert.ToDecimal(1)
- _nudWorkTimes.Maximum = 100
- _nudWorkTimes.Minimum = 1
- _nudWorkTimes.Increment = 1
- _nudWorkTimes.Location = New Point(110, 40)
- _nudWorkTimes.Size = New Size(50, 20)
- Me.Controls.Add(_nudWorkTimes)
- End Sub
- Private Sub CreateLabel_Log()
- _lblLog = New Label
- _lblLog.Name = "lblLog"
- _lblLog.Text = "Log:"
- _lblLog.Location = New Point(10, 130)
- _lblLog.Size = New Size(50, 20)
- Me.Controls.Add(_lblLog)
- End Sub
- Private Sub CreateListBox_Log()
- _lsbLog = New ListBox
- _lsbLog.Name = "lsbLog"
- _lsbLog.Location = New Point(10, 150)
- _lsbLog.Size = New Size(600, 300)
- _lsbLog.ScrollAlwaysVisible = True
- _lsbLog.HorizontalScrollbar = True
- Me.Controls.Add(_lsbLog)
- End Sub
- #End Region
- #End Region
- End Class
- Public Class TestTaskClass
- 'Public taskWorker As Task(Of Double)
- Public taskWorker As Task(Of Double())
- Public threadID As Integer
- Public result As Double
- Sub New()
- End Sub
- End Class
沒有留言:
張貼留言