如何等待多个BackgroundWorker完成,防止主线程在数组转置完成前执行?
解决主线程等待多线程数组转置完成的问题
你的思路是对的——用多线程并行处理数组转置来提速,但现有代码的核心问题是主线程没有办法感知所有转置任务的完成状态,也没有收集转置后的结果。下面我给你两种可行的解决方案,一种基于你现有的BackgroundWorker改进,另一种是更现代的TPL(任务并行库)方案,后者代码更简洁,维护成本更低。
方案一:基于BackgroundWorker的改进版
我们需要添加三个关键组件来实现主线程等待:
- 一个线程安全的信号量(
CountdownEvent),用来跟踪所有任务的完成情况 - 一个线程安全的集合,用来存储转置后的数组结果
- 在主线程中调用信号量的
Wait()方法,阻塞直到所有任务完成
修改后的完整代码
首先在你的类中添加类级别的线程安全变量:
' 线程安全的信号量,初始计数等于要处理的数组总数 Private waitHandle As CountdownEvent ' 存储转置结果的线程安全队列(如果需要保持原数组顺序,可改用ConcurrentDictionary) Private transposedResults As New Concurrent.ConcurrentQueue(Of Single(,))
然后修改启动线程的方法:
Private Sub RunXTransposingThreads(ParamArray ArraysToTranspose() As Array) Dim totalArrays = ArraysToTranspose.Length ' 初始化信号量,计数为数组总数 waitHandle = New CountdownEvent(totalArrays) ' 清空结果队列 transposedResults.Clear() For i = 0 To totalArrays - 1 Dim worker As New System.ComponentModel.BackgroundWorker ' 传递数组和索引(方便后续对应原数组顺序,可选) Dim taskState = New With { .Index = i, .InputArray = CType(ArraysToTranspose(i), Single(,)) } AddHandler worker.DoWork, AddressOf RunOneThread AddHandler worker.RunWorkerCompleted, AddressOf HandleThreadCompletion worker.RunWorkerAsync(taskState) Next ' 阻塞主线程,直到所有转置任务完成 waitHandle.Wait() ' 到这里所有数组都转置完成了,主线程可以继续处理结果 Debug.Print($"所有转置任务完成,共处理 {transposedResults.Count} 个数组") End Sub
更新转置任务的执行方法:
Private Sub RunOneThread(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Dim state = CType(e.Argument, Object) Dim inputArray = state.InputArray ' 执行转置 Dim transposedArray = Transpose2dArray(inputArray) ' 返回结果和原索引(可选) e.Result = New With { .Index = state.Index, .TransposedArray = transposedArray } End Sub
更新任务完成后的处理方法:
Private Sub HandleThreadCompletion(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) If e.Error IsNot Nothing Then ' 处理线程执行中的异常,避免主线程无限等待 Debug.Print($"转置任务出错:{e.Error.Message}") Else Dim result = CType(e.Result, Object) ' 将结果存入线程安全队列 transposedResults.Enqueue(result.TransposedArray) Debug.Print($"第 {result.Index + 1} 个数组转置完成") End If ' 通知信号量:一个任务已完成 waitHandle.Signal() End Sub
优化你的转置函数(修正泛型命名,避免和系统Array关键字冲突):
Function Transpose2dArray(Of T)(ByVal inArray As T(,)) As T(,) Dim cols = inArray.GetLength(1) Dim rows = inArray.GetLength(0) Dim outArray(cols - 1, rows - 1) As T ' 如果你想进一步提速,可以用Parallel.For并行处理列 Parallel.For(0, cols, Sub(i) For j = 0 To rows - 1 outArray(i, j) = inArray(j, i) Next End Sub) Return outArray End Function
方案二:用TPL(任务并行库)简化代码
BackgroundWorker是.NET Framework早期的组件,现在更推荐用Task并行库(TPL),它支持Async/Await语法,代码更简洁,不需要手动管理线程和信号量,还能更好地处理异步场景(比如UI线程不阻塞)。
完整代码示例
' 异步方法,返回所有转置后的数组列表 Private Async Function RunXTransposingTasks(ParamArray ArraysToTranspose() As Array) As Task(Of List(Of Single(,))) Dim transposeTasks As New List(Of Task(Of Single(,))) ' 为每个数组创建转置任务 For Each arr In ArraysToTranspose Dim inputArray = CType(arr, Single(,)) transposeTasks.Add(Task.Run(Function() Transpose2dArray(inputArray))) Next ' 等待所有任务完成,并获取所有结果 Dim allResults = Await Task.WhenAll(transposeTasks) Return allResults.ToList() End Function
在主线程中调用(以WinForms为例)
如果是UI应用,用Async Sub避免阻塞UI:
Private Async Sub btnStartTranspose_Click(sender As Object, e As EventArgs) Handles btnStartTranspose.Click ' 假设Arr1、Arr2...是你的Single(,)类型数组 Dim transposedArrays = Await RunXTransposingTasks(Arr1, Arr2, Arr3) ' 所有数组转置完成,在这里处理结果 Debug.Print($"所有转置完成,共 {transposedArrays.Count} 个数组") End Sub
如果是控制台应用或非UI线程,可以直接用.Result等待:
Sub Main() Dim transposedArrays = RunXTransposingTasks(Arr1, Arr2, Arr3).Result ' 处理转置后的数组 End Sub
额外性能优化建议
对于1024×128的大型数组,你还可以:
- 用
Buffer.BlockCopy代替逐元素赋值(仅适用于值类型数组,比如Single),但注意转置时内存布局的问题,需要调整复制逻辑 - 限制并行线程数,避免线程过多导致上下文切换开销:比如用
Task.Run时指定TaskCreationOptions.LongRunning,或者用ParallelOptions设置MaxDegreeOfParallelism
内容的提问来源于stack exchange,提问作者Noldor130884
相关产品推荐
相关产品推荐

