burnersk
burnersk

Reputation: 3500

How to wait for all threads to be finish without blocking the UI in VB.NET?

I have a large initialization routine within a VB.NET Framework 4.0 application, that I want to optimize for run time. As most computers nowadays are capable of performing multiple threads at a time, I want to introduce multi-threading to perform non-dependent in parallel, so the overall time to the user it takes for initializing the application is reduced.

The original (single thread) implementation looks like this:

Friend Module MainModule

    Friend Sub Main()
        LongRunningInitialization1()
        LongRunningInitialization2()
        LongRunningInitialization3()
        LongRunningInitialization4()
        LongRunningInitialization5()
        LongRunningInitialization6()
        LongRunningInitialization7()
    End Sub

    Private Sub LongRunningInitialization1()
    End Sub

    Private Sub LongRunningInitialization2()
    End Sub

    Private Sub LongRunningInitialization3()
    End Sub

    Private Sub LongRunningInitialization4()
    End Sub

    Private Sub LongRunningInitialization5()
    End Sub

    Private Sub LongRunningInitialization6()
    End Sub

    Private Sub LongRunningInitialization7()
    End Sub

End Module

My first approach was it to use System.Threading.Thread to parallelize the workloads:

Friend Sub Main()
    Dim thread1 As New Threading.Thread(AddressOf LongRunningInitialization1)
    Dim thread2 As New Threading.Thread(AddressOf LongRunningInitialization2)
    Dim thread3 As New Threading.Thread(AddressOf LongRunningInitialization3)
    Dim thread4 As New Threading.Thread(AddressOf LongRunningInitialization4)
    Dim thread5 As New Threading.Thread(AddressOf LongRunningInitialization5)
    Dim thread6 As New Threading.Thread(AddressOf LongRunningInitialization6)
    Dim thread7 As New Threading.Thread(AddressOf LongRunningInitialization7)
    thread1.Join()
    thread2.Join()
    thread3.Join()
    thread4.Join()
    thread5.Join()
    thread6.Join()
    thread7.Join()
End Sub

While this rudimentary works, Join() is a blocking call and in a screnario in which thread1 takes the longest time, the other threads are "zombies" as long as thread1 has finished. Also a not here implemented splash screen with a progress bar freezes as Join() is a blocking call.

I came up with another "solution" by using a while and theThreadState` property of a thread to do non-blocking wait in a hacky way:

Friend Sub Main()
    Dim thread1 As New Threading.Thread(AddressOf LongRunningInitialization1)
    Dim thread2 As New Threading.Thread(AddressOf LongRunningInitialization2)
    Dim thread3 As New Threading.Thread(AddressOf LongRunningInitialization3)
    Dim thread4 As New Threading.Thread(AddressOf LongRunningInitialization4)
    Dim thread5 As New Threading.Thread(AddressOf LongRunningInitialization5)
    Dim thread6 As New Threading.Thread(AddressOf LongRunningInitialization6)
    Dim thread7 As New Threading.Thread(AddressOf LongRunningInitialization7)
    While thread1.ThreadState = Threading.ThreadState.Running _
        OrElse thread2.ThreadState = Threading.ThreadState.Running _
        OrElse thread3.ThreadState = Threading.ThreadState.Running _
        OrElse thread4.ThreadState = Threading.ThreadState.Running _
        OrElse thread5.ThreadState = Threading.ThreadState.Running _
        OrElse thread6.ThreadState = Threading.ThreadState.Running _
        OrElse thread7.ThreadState = Threading.ThreadState.Running
        Application.DoEvents() ' process the Windows Forms message queue.
        Threading.Thread.Sleep(1) ' non-blocking wait.
    End While
    thread1.Join()
    thread2.Join()
    thread3.Join()
    thread4.Join()
    thread5.Join()
    thread6.Join()
    thread7.Join()
End Sub

While this last implementation works in this example, it is not usable when you face the real world with around 20 long running initialization tasks that should mostly run in parallel. The while condition would be epic in size.

Is there a solution to perform a non-blocking wait for all the threads which where created? Something like the following pseudo code:

Friend Sub Main()
    Dim threadPool As New PseudoThreadPool()
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization1))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization2))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization3))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization4))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization5))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization6))
    threadPool.Add(New Threading.Thread(AddressOf LongRunningInitialization7))
    threadPool.Start()
    While threadPool.AllDone = False
        Application.DoEvents() ' process the Windows Forms message queue.
        Threading.Thread.Sleep(1) ' non-blocking wait.
    End While
    threadPool.Join()
End Sub

Upvotes: 0

Views: 504

Answers (1)

burnersk
burnersk

Reputation: 3500

I have found a solution, thanks to @Jimi (List(Of Task) approach).

Friend Sub Main()
    Dim tasks As New List(Of Task)
    tasks.Add(New Task(AddressOf LongRunningInitialization1))
    tasks.Add(New Task(AddressOf LongRunningInitialization2))
    tasks.Add(New Task(AddressOf LongRunningInitialization3))
    tasks.Add(New Task(AddressOf LongRunningInitialization4))
    tasks.Add(New Task(AddressOf LongRunningInitialization5))
    tasks.Add(New Task(AddressOf LongRunningInitialization6))
    tasks.Add(New Task(AddressOf LongRunningInitialization7))
    ' Start all tasks.
    tasks.All(
        Function(t As Task)
            t.Start()
            Return True
        End Function
    )
    ' Wait until all tasks has been finished.
    While tasks.Any(Function(t As Task) Not (t.Status = TaskStatus.Canceled OrElse t.Status = TaskStatus.Faulted OrElse t.Status = TaskStatus.RanToCompletion))
        Application.DoEvents()
        Sleep(1)
    End While
End Sub

Upvotes: 1

Related Questions