VB.NET 异步调用与WaitHandle使用问题解决方案

在之前的一篇文章中,讨论了如何使用异步调用来保持用户界面的响应性。这次,遇到了一个稍微复杂一点的问题:在VB.NET中,如何异步调用同一个方法多次,并阻塞直到所有线程完成。这个问题在VB.NET中并不简单,让一步步来探讨。

为了测试方法,创建了一个简单的VB.NET控制台应用程序。在这个程序中,创建了一个包含大约25个名字的字符串数组,然后创建了一个WaitHandle数组。接下来,使用for…each循环遍历数组,同时调用委托BeginInvoke,并把返回的IAsyncResult添加到WaitHandle数组中。然后,立即调用WaitHandle.WaitAll来阻塞当前线程,直到所有线程完成。委托指向一个简单的子程序,它将名字写入控制台窗口。这个子程序还会检查名字是否为"Ryan",如果是,则暂停线程。这样做是为了在控制台窗口中看到异步调用的效果。

代码示例

以下是VB.NET中的代码示例:

Private Sub WriteName(ByVal p_Name As String) If p_Name = "Ryan" Then Thread.Sleep(2000) End If Console.WriteLine(p_Name) End Sub Private Sub UsingWaitAll() Dim names As String() = GetNames(200) Dim waitHandles As List(Of WaitHandle) = New List(Of WaitHandle)() Dim caller As WriteNameHandler = New WriteNameHandler(AddressOf WriteName) For Each name As String In names Dim results As IAsyncResult = caller.BeginInvoke(name, Nothing, Nothing) waitHandles.Add(results.AsyncWaitHandle) Next WaitHandle.WaitAll(waitHandles.ToArray()) Console.WriteLine("Done") End Sub

遇到的问题

按照预期,所有新线程都应该异步启动,WaitAll调用将阻塞。一旦所有线程完成任务,它们应该发出信号,然后WaitHandle继续进一步处理。但事实并非如此,遇到了一个异常:“WaitAll在STA线程上不支持多个句柄。”

解决方案

在研究这个问题时,发现了几种解决方法。由于对这个问题的本质理解不够深入,很难说哪种解决方案是可行的。以下是找到的三种解决方案:

第一种解决方案是将Sub Main()的属性更改为MTAThreadAttribute()。从STA(单线程单元)更改为MTA(多线程单元)后,应用程序可以正常完成,然后结束,没有问题。

<MTAThreadAttribute()> _ Sub Main() ' some code here End Sub

第二种解决方案是使用自定义的WaitAll方法。这个方法检查当前线程是否为STA线程,如果是,则循环遍历WaitHandle数组,调用WaitAny方法。

Private Sub UsingWaitAllTrick() Dim names As String() = GetNames(100) Dim waitHandles As List(Of WaitHandle) = New List(Of WaitHandle)() Dim caller As WriteNameHandler = New WriteNameHandler(AddressOf WriteName) For Each name As String In names Dim results As IAsyncResult = caller.BeginInvoke(name, Nothing, Nothing) waitHandles.Add(results.AsyncWaitHandle) Next WaitAll(waitHandles.ToArray()) End Sub Sub WaitAll(ByVal waitHandles() As WaitHandle) If Thread.CurrentThread.GetApartmentState() = ApartmentState.STA Then For Each waitHandle As WaitHandle In waitHandles waitHandle.WaitAny(New WaitHandle() {waitHandle}) Next Else WaitHandle.WaitAll(waitHandles) End If End Sub Dim manualResetEvent As ManualResetEvent Dim remainingWorkItems As Integer = 0 Dim objLock1 As Object = New Object Private Sub UsingWaitAllManualResetEvent() manualResetEvent = New ManualResetEvent(False) Dim names As String() = GetNames(100) For Each name As String In names SyncLock objLock1 remainingWorkItems += 1 End SyncLock ThreadPool.QueueUserWorkItem(AddressOf WriteNameManualResetEvent, name) Next manualResetEvent.WaitOne() Console.WriteLine("Done") End Sub
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485