Let's say there is a nested loop where each item has some processing time, each item should be reported (to UI) when finished and the items must be reported in work order. Here is pseudo code of the problem:
Progress report must be in order A1:B1-B2-B3, A2:B1-B2, etc. Cancellation is also desirable.
It is tempting to do this with a BackgroundWorker, it has easy to use progress reporting to UI thread and can also be set up with cancellation, but it will only use a single thread. This example show how the items can be processed with multiple threads and still report progress in correct order using tasks (TPL).
First set up a test job class, this uses a Task to wait for before reporting progress, which is of course the Task that was started before it in loop. Processing is just a simulation with sleep time.
The test code uses regular loops and sets up jobs with ID to see that loop order is followed.
Adding cancellation to this is not hard, a CancellationTokenSource is the key ingredient in TPL for this. A list is used to hold all tasks and after loop these are waited for in Try-Catch to see the TaskCanceledException when Cancel method of CancellationTokenSource is called.
Testing cancellation you may do this:
Adding some debug time measurement to code and the output could look like this:
Feedback is welcome, also, would there be a different way of solving the problem at hand using other functionality of TPL?
VB.NET:
for A
for B
process AB
report progress AB
It is tempting to do this with a BackgroundWorker, it has easy to use progress reporting to UI thread and can also be set up with cancellation, but it will only use a single thread. This example show how the items can be processed with multiple threads and still report progress in correct order using tasks (TPL).
First set up a test job class, this uses a Task to wait for before reporting progress, which is of course the Task that was started before it in loop. Processing is just a simulation with sleep time.
VB.NET:
Public Class TestJob
Public Property ID As String
Public Property WaitTask As Task
Public Sub ProcessSimple(time As Integer, reporter As IProgress(Of String))
Thread.Sleep(time) 'processing
WaitTask?.Wait()
reporter.Report(ID)
End Sub
End Class
VB.NET:
Private reporter As New Progress(Of String)(Sub(status) Debug.WriteLine(status))
Private Sub TestSequenceSimple()
Dim rnd As New Random
Dim currentTask As Task = Nothing
For A = 1 To 3
For B = 1 To 3
Dim job As New TestJob With {.ID = $"(A{A},B{B})", .WaitTask = currentTask}
Dim processingTime = rnd.Next(3000, 5000)
currentTask = Task.Run(Sub() job.ProcessSimple(processingTime, reporter))
Next
Next
End Sub
VB.NET:
Private cancelSource As New CancellationTokenSource
Private Async Sub TestSequenceCancel()
Dim rnd As New Random
Dim currentTask As Task = Nothing
Dim allTasks As New List(Of Task)
For A = 1 To 3
For B = 1 To 3
Dim job As New TestJob With {.ID = $"(A{A},B{B})", .WaitTask = currentTask}
Dim processingTime = rnd.Next(3000, 5000)
currentTask = Task.Run(Sub() job.ProcessSimple(processingTime, reporter), cancelSource.Token)
allTasks.Add(currentTask)
Next
Next
Try
Await Task.WhenAll(allTasks)
Catch ex As TaskCanceledException
Debug.WriteLine("operation cancelled")
End Try
End Sub
VB.NET:
TestSequenceCancel()
Thread.Sleep(1000)
cancelSource.Cancel()
This means what would have taken 35 seconds with a BackgroundWorker now takes only 8 seconds.(A1,B1): processing 3594, wait 0
(A1,B2): processing 4849, wait 0
(A1,B3): processing 3654, wait 1194
(A2,B1): processing 4580, wait 269
(A2,B2): processing 3009, wait 836
(A2,B3): processing 3987, wait 0
(A3,B1): processing 4623, wait 0
(A3,B2): processing 3585, wait 438
(A3,B3): processing 3097, wait 25
total processing time 34978 finished in 7721
Feedback is welcome, also, would there be a different way of solving the problem at hand using other functionality of TPL?