Tasks, Parallelism, and Cancellation
Prerequisites: Async and await
Async/Await is for waiting efficiently. For CPU-heavy work —
calculations, image processing — you can split the work across
processor cores.
Parallel.For and Task.Run
Imports System.Threading
Imports System.Threading.Tasks
Module Program
Function IsPrime(n As Integer) As Boolean
If n < 2 Then Return False
For d = 2 To CInt(Math.Sqrt(n))
If n Mod d = 0 Then Return False
Next
Return True
End Function
Sub Main()
Dim count = 0
Parallel.For(1, 200001, Sub(n)
If IsPrime(n) Then Interlocked.Increment(count)
End Sub)
Console.WriteLine($"primes below 200001: {count}")
Dim squares = Enumerable.Range(1, 10).AsParallel().AsOrdered().Select(Function(x) x * x).ToArray()
Console.WriteLine(String.Join(" ", squares))
Dim total = Task.Run(Function() Enumerable.Range(1, 1000).Sum()).Result
Console.WriteLine(total)
End Sub
End Module
primes below 200001: 17984
1 4 9 16 25 36 49 64 81 100
500500
Parallel.Forruns the loop body on several threads at once. Because many threads updatecount, it must useInterlocked.Increment— a plaincount += 1would lose updates.AsParallel()(PLINQ) runs a LINQ query in parallel;AsOrdered()keeps the original order.Task.Runmoves work to a background thread — in a desktop app,Await Task.Run(...)keeps the UI responsive during a long calculation.
Protecting shared data
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Module Program
Sub Main()
Dim counts As New ConcurrentDictionary(Of Integer, Integer)
Parallel.For(0, 10000, Sub(i)
counts.AddOrUpdate(i Mod 3, 1, Function(key, old) old + 1)
End Sub)
For Each key In counts.Keys.OrderBy(Function(k) k)
Console.WriteLine($"{key}: {counts(key)}")
Next
Dim gate As New Object()
Dim total = 0
Parallel.For(1, 101, Sub(i)
SyncLock gate
total += i
End SyncLock
End Sub)
Console.WriteLine(total)
End Sub
End Module
0: 3334
1: 3333
2: 3333
5050
SyncLock lets only one thread at a time run the enclosed code.
Collections in System.Collections.Concurrent are safe to use from
many threads.
Cancellation
Long operations should be cancellable. Pass a CancellationToken and
check it:
Imports System.Threading
Module Program
Async Function CountAsync(token As CancellationToken) As Task
For i = 1 To 10
Await Task.Delay(200, token)
Console.WriteLine($"tick {i}")
Next
End Function
Sub Main()
Using cts As New CancellationTokenSource(500) ' cancel after 500 ms
Try
CountAsync(cts.Token).GetAwaiter().GetResult()
Catch ex As OperationCanceledException
Console.WriteLine("cancelled")
End Try
End Using
End Sub
End Module
tick 1
tick 2
cancelled
In a desktop app, a Cancel button calls cts.Cancel(). Most .NET async
methods (HttpClient, file and database APIs) accept a token.
Reporting progress
IProgress(Of T) reports progress back to the caller — in Windows
Forms, the callback runs on the UI thread, so it can update a
ProgressBar directly:
Module Program
Async Function ProcessAsync(progress As IProgress(Of Integer)) As Task
For percent = 25 To 100 Step 25
Await Task.Delay(50)
progress.Report(percent)
Next
End Function
Sub Main()
Dim reporter = New Progress(Of Integer)(Sub(p) Console.WriteLine($"{p}%"))
ProcessAsync(reporter).GetAwaiter().GetResult()
Threading.Thread.Sleep(100) ' let the last report print in a console app
End Sub
End Module
25%
50%
75%
100%