与BeginInvoke一起使用AsyncCallback时,为什么我在回调方法中得到“跨线程操作无效异常”?

时间:2009-07-28 15:29:10

标签: vb.net multithreading

我有一个Windows窗体,通过串口从秤中获取数据。由于我需要操作员能够取消该过程,所以我在第二个线程上执行get数据处理。

数据收集过程一次从秤中获取多个读数。表单有一个标签,需要使用特定于每次阅读的信息进行更新。

我使用此代码调用方法从秤中获取数据。

Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)

readALine方法的委托在UI代码中定义。

Delegate Function readALine(ByVal toSend As String) As String

GetDataCallback方法:

Private Sub GetDataCallback(ByVal ia As IAsyncResult)
    .
    .
    .
    lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
    lblInstructions.Refresh()
    .
    .
    .
End Sub

我在“lblInstructions.Text =”语句中得到了异常。

我认为GetDataCallback方法是UI线程的一部分,所以不明白我为什么会得到异常。我知道这可能是使用BackgroundWorker重写的,这是适当的事件,但现在想了解为什么这不能按预期工作。

该应用程序最初是在VS2003中编写的,最近刚升级到VS2008。

任何见解都将受到赞赏。

谢谢, 戴夫

5 个答案:

答案 0 :(得分:3)

问题是对BeginInvoke的混淆。调用Control.BeginInvoke封送对UI线程的委托调用。在委托上调用BeginInvoke 会导致它在线程池线程上执行 - 并且您提供的任何回调都在同一个(线程池)线程上执行。后者就是你正在做的,这就是为什么GetDataCallback正在线程池线程上执行,而不是UI线程..

因此,在GetDataCallback中,您需要调用Control.InvokeControl.BeginInvoke来封送回UI线程。

有一点需要注意:这些天我很少使用Control.InvokeRequired - 无条件调用Invoke / BeginInvoke更为简单;根据我的经验,性能差异通常不足以弥补可读性的好处。

答案 1 :(得分:1)

用户界面控件只能由创建它们的线程更新

yourForm.BeginInvoke

代替;应该将调用编组到正确的线程

答案 2 :(得分:0)

在.NET 1.1中,可以执行这些跨线程操作,即使它们不安全。

在.NET 2.0中,当您尝试执行此类的跨线程操作时,抛出了您提到的异常,这意味着您正在非UI线程上执行UI操作,即使您认为自己并非如此吨。

尝试使用Me.InvokeRequired方法确定您当前是否在UI线程上。例如。您可以定义一个方法来执行必要的任务:

Protected Sub PerformUIOperations()
  If Me.InvokeRequired Then
    'We are currently on a non-UI thread. Invoke this method on the UI thread:
    Me.Invoke(New MethodInvoker(AddressOf Me.PerformUIOperations))
    Return
  End If
  'Perform UI operations when we know it is safe:
  lblInstructions.Text = "blabla"
End Sub

然后可以从任何非UI线程调用PerformUIOperations方法,因为它负责在正确的线程本身上执行任务。

当然,如果您需要将参数传递给PerformUIOperations方法(例如有关正在进行的操作的信息),您必须定义一个委托以匹配PerformUIOperations方法签名并使用它而不是MethodInvoker。

答案 3 :(得分:0)

乔,

我在代码的另一部分中有以下内容:

Delegate Sub setValueCallback(ByVal row As Integer, ByVal value As Decimal)
Public Sub setValue(ByVal row As Integer, ByVal value As Decimal)
    If Me.Controls.Item(_tbIndex(row - 1)).InvokeRequired Then
        Dim d As New setValueCallback(AddressOf setValue)
        Me.Invoke(d, New Object() {row, value})
    Else
        Dim tb As TextBox = Me.Controls.Item(_tbIndex(row - 1))
        tb.Text = value
        _dt.Rows(tb.Tag).Item(1) = value
    End If
End Sub

如何将其重写为不使用.InvokeRequired?

答案 4 :(得分:0)

戴夫,也许这就是你要找的解决方案:

Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)

Delegate Function readALine(ByVal toSend As String) As String

Private Sub GetDataCallback(ByVal ia As IAsyncResult)
   If lblInstructions.InvokeRequired Then
      lblInstructions.Invoke(New AsyncCallback(AddressOf GetDataCallback), New Object() {ia})
   Else
     .
     .
     lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
     lblInstructions.Refresh()
     .
     .
  End If
End Sub