VB.NET试图修改一个通用的Invoke方法到一个通用的BeginInvoke方法,有意想不到的问题 [英] VB.NET Trying to modify a generic Invoke method to a generic BeginInvoke method, having unexpected problems

查看:2956
本文介绍了VB.NET试图修改一个通用的Invoke方法到一个通用的BeginInvoke方法,有意想不到的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

VB.NET 2010,.NET 4

VB.NET 2010, .NET 4

您好,

我一直在使用从后台线程UI更新pretty光滑通用invoke方法。我忘了,我从(转换它从VB.NET C#)复制,但在这里它是:

I have been using a pretty slick generic invoke method for UI updating from background threads. I forget where I copied it from (converted it to VB.NET from C#), but here it is:

Public Sub InvokeControl(Of T As Control)(ByVal Control As t, ByVal Action As Action(Of t))
    If Control.InvokeRequired Then
        Try
            Control.Invoke(New Action(Of T, Action(Of T))(AddressOf InvokeControl), New Object() {Control, Action})
        Catch ex As Exception
        End Try
    Else
        Action(Control)
    End If
End Sub

现在,我想修改本作如果要求没有调用(或抛出异常),或者如果需要调用的IAsyncResult从返回的BeginInvoke不返回任何内容的功能。下面是我有:

Now, I want to modify this to make a function that returns Nothing if no invoke was required (or an exception was thrown) or the IAsyncResult returned from BeginInvoke if invoke was required. Here's what I have:

Public Function InvokeControl(Of T As Control)(ByVal Control As t, ByVal Action As Action(Of t)) As IAsyncResult
    If Control.InvokeRequired Then
        Try
            Return Control.BeginInvoke(New Action(Of T, Action(Of T))(AddressOf InvokeControl), New Object() {Control, Action})
        Catch ex As Exception
            Return Nothing
        End Try
    Else
        Action(Control)
        Return Nothing
    End If
End Function

我想这样做主要是为了避免阻塞。问题是,拨打电话的时候,我现在得到的错误,像这样的:

I wanted to do this primarily to avoid blocking. The problem is that I now get errors when making calls such as this:

InvokeControl(SomeTextBox, Sub(x) x.Text = "Some text")

这与原来的调用(不是的BeginInvoke)方法工作得很好。现在,我得到一个对象引用不设置到对象的实例异常。如果我把手表上SomeTextBox,它说:

This worked fine with the original Invoke (rather than BeginInvoke) method. Now I get a "Object reference not set to an instance of an object" exception. If I put a watch on SomeTextBox, it says

SomeTextBox {Text = (Text) threw an exception of type Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException.}

这可能是相关的,这样InvokeControl调用来自System.Timers.Timer的消逝事件中。其间隔时间为500ms,这应该是足够长的多为UI更新完成(如果该事项)。这是怎么回事?

It might be relevant that such InvokeControl calls come from within a System.Timers.Timer's Elapsed event. Its Interval is 500ms, which should be more than long enough for the UI updates to complete (if that matters). What is going on?

在此先感谢您的帮助!

编辑:更多细节

下面是我System.Timer.Timer的消逝处理程序:

Here is my System.Timer.Timer's Elapsed handler:

Private Sub MasterTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles MasterTimer.Elapsed
    MasterTimer.Enabled = False
    If Not MasterTimer.Interval = My.Settings.TimingMasterTimerInterval Then
        MasterTimer.Interval = My.Settings.TimingMasterTimerInterval
        NewEventLogEntry("The master timer's interval has been changed to " & MasterTimer.Interval.ToString & " milliseconds.")
    End If
    InvokeControl(TimerPictureBox, Sub(x) x.Toggle(True))
    ReadFromDevices()
    UpdateIndicators()
    'This block is not executing when the error is thrown
    If Mode > RunMode.NotRunning Then
        UpdateProcessTime()
        UpdateRemainingTime()
        UpdateStatusTime()
    End If
    'This block is not executing when the error is thrown
    If Mode = RunMode.Running Then
        CheckMillerCurrent()
        CheckTolerances()
    End If

    MasterTimer.Enabled = True
End Sub

Private Sub ReadFromDevices()
    For Each dev As Device In Devices
        Try
            If dev.GetType.Equals(GetType(Miller)) Then
                Dim devAsMiller As Miller = CType(dev, Miller)
                With devAsMiller
                    If .PowerOn.Enabled Then .PowerOn.Read()
                    If .CurrentRead.Enabled Then .CurrentRead.Read()
                    If .VoltageRead.Enabled Then .VoltageRead.Read()
                    If .Trigger.Enabled Then .Trigger.Read()
                    If .Shutter.Enabled Then .Shutter.Read()
                End With
            ElseIf dev.GetType.Equals(GetType(SubstrateBiasVoltage)) Then
                Dim devAsSubstrateBiasVoltage As SubstrateBiasVoltage = CType(dev, SubstrateBiasVoltage)
                With devAsSubstrateBiasVoltage
                    If .LambdaCurrentRead.Enabled Then .LambdaCurrentRead.Read()
                    If .LambdaVoltageRead.Enabled Then .LambdaVoltageRead.Read()
                    If .BiasResistor.Enabled Then .BiasResistor.Read()
                    If .Pinnacle.Enabled Then .Pinnacle.Read()
                End With
            Else
                If dev.Enabled Then dev.Read()
            End If
        Catch ex As Exception
            NewEventLogEntry("An error occurred while trying to read from a device.", ex, EventLogItem.Types.Warning)
        End Try
    Next
End Sub

Private Sub UpdateIndicators()
    Dim ObjLock As New Object
    SyncLock ObjLock
        With Devices
            InvokeControl(EmergencyStopPictureBox, Sub(x As DigitalPictureBox) x.Toggle(Mode > RunMode.NotRunning))

            InvokeControl(MillerCurrentIndicator, Sub(x) x.Text = .Miller1.CurrentRead.GetParsedValue.ToString)
            InvokeControl(MillerVoltageIndicator, Sub(x) x.Text = .Miller1.VoltageRead.GetParsedValue.ToString)
            With .SubstrateBiasVoltage
                InvokeControl(LambdaVoltageIndicator, Sub(x) x.Text = .LambdaVoltageRead.GetParsedValue.ToString)
                InvokeControl(LambdaCurrentIndicator, Sub(x) x.Text = .LambdaCurrentRead.GetParsedValue.ToString)
                InvokeControl(PinnacleVoltageIndicator, Sub(x) x.Text = .Pinnacle.GetParsedValue.ToString)
                InvokeControl(PinnacleCurrentIndicator, Sub(x) x.Text = .Pinnacle.ReadCurrent.ToString)
            End With
            InvokeControl(HeaterPowerIndicator, Sub(x) x.Text = .HeaterPower.GetParsedValue.ToString)
            InvokeControl(ConvectronIndicator, Sub(x) x.Text = .Convectron.GetParsedValue.ToString)
            If .Baratron.GetParsedValue > 200 Then
                InvokeControl(BaratronIndicator, Sub(x) x.Text = "OFF")
            Else
                InvokeControl(BaratronIndicator, Sub(x) x.Text = .Baratron.GetParsedValue.ToString)
            End If
            If .Ion.GetParsedValue > 0.01 Then
                InvokeControl(IonIndicator, Sub(x) x.Text = "OFF")
            Else
                InvokeControl(IonIndicator, Sub(x) x.Text = .Ion.GetParsedValue.ToString)
            End If
            InvokeControl(ArgonFlowRateIndicator, Sub(x) x.Text = .ArgonFlowRate.GetParsedValue.ToString)
            InvokeControl(NitrogenFlowRateIndicator, Sub(x) x.Text = .NitrogenFlowRate.GetParsedValue.ToString)
            InvokeControl(GateValvePositionIndicator, Sub(x) x.Text = .GateValvePosition.GetParsedValue.ToString)

            InvokeControl(RoughingPumpPowerOnIndicator, Sub(x As PowerButton) x.IsOn = .RoughingPumpPowerOn.Value = Power.On)

            ToggleImageList(.Miller1.CurrentRead.ImageList, .Miller1.CurrentRead.GetParsedValue > My.Settings.MinimumMillerCurrent)
            ToggleImageList(.Miller1.Trigger.ImageList, .Miller1.Trigger.GetParsedValue = Power.On)
            ToggleImageList(.HeaterPower.ImageList, .HeaterPower.Value > 0)
            With .SubstrateBiasVoltage
                ToggleImageList(.LambdaVoltageRead.ImageList, .LambdaVoltageRead.GetParsedValue > 0 And .BiasResistor.GetParsedValue = BiasResistor.Lambda)
                ToggleImageList(.Pinnacle.ImageList, .Pinnacle.GetParsedValue > 10 And .BiasResistor.GetParsedValue = BiasResistor.Pinnacle)
            End With
            ToggleImageList(.ArgonValveOpen.ImageList, .ArgonValveOpen.Value = Valve.Open)
            ToggleImageList(.NitrogenValveOpen.ImageList, .NitrogenValveOpen.Value = Valve.Open)
            ToggleImageList(.RoughingPumpValveOpen.ImageList, .RoughingPumpValveOpen.Value = Valve.Open)
            ToggleImageList(.SlowPumpDownValve.ImageList, .SlowPumpDownValve.Value = Valve.Open)
            ToggleImageList(.RotationPowerOn.ImageList, .RotationPowerOn.Value = Power.On)
            ToggleImageList(.WaterMonitor1.ImageList, .WaterMonitor1.Value = Power.On And .WaterMonitor2.Value = Power.On)
            ToggleImageList(.GateValvePosition.ImageList, .GateValvePosition.SetValue > 0)
        End With
    End SyncLock
End Sub

Private Sub ToggleImageList(ByRef ImageList As ImageList, ByVal IsOn As Boolean)
    For Each img As OnOffPictureBox In ImageList
        SafeInvokeControl(img, Sub(x As OnOffPictureBox) x.Toggle(IsOn))
    Next
End Sub

我希望这不是TMI,但希望它会帮助我们发现了什么走错了。

I hope that's not TMI, but hopefully it'll help spot what going wrong.

此外,对文本框和一些断点之一的手表,我发现该错误是神奇地抛出的之后的ReadFromDevices不过的的UpdateIndicators。到那个,我的意思是,在ReadFromDevices的最后一个断点显示文本框都没有抛出的错误,但在UpdateIndicators的开头(已作出任何InvokeControl调用之前)断点表明,他们有...

Also, with the watch on one of the textboxes and some breakpoints, I found that the error is somehow magically thrown after ReadFromDevices but before UpdateIndicators. By that, I mean that a breakpoint at the very end of ReadFromDevices shows the textboxes haven't thrown the error, but a breakpoint at the beginning of UpdateIndicators (before any InvokeControl calls have been made) shows that they have...

推荐答案

这是很难用调试捕获异常,因为它会在多中的任何一个发生 PostMessage的调用到UI消息泵(由 InvokeControl &功放;在的BeginInvoke 电话)。 Visual Studio将有一个很难打破的异常上。这可能是为什么它看起来异常被神奇地被抛出。

It is difficult to use debugging to catch the exception as it will occur on any one of multiple PostMessage calls to the UI message pump (caused by InvokeControl & the BeginInvoke calls). Visual Studio will have a hard time breaking on the exception. This is possibly why it appears that the exception is "magically" being thrown.

您的问题不在于你的执行 InvokeControl ,但在 UpdateIndicators 方法。这是因为使用了语句和异步UI线程调用像这样的:

Your issue lies not in your implementation of InvokeControl but in the UpdateIndicators method. It is because of the use of the With statement and the asynchronous UI thread calls like this:

With Devices
    ...
    InvokeControl(MillerCurrentIndicator, Sub(x) x.Text = .Miller1.CurrentRead.GetParsedValue.ToString)
    ... 
 End With

由于该子(X) $正在被张贴在UI消息线程它在UI线程上执行C $ c是极有可能调用$ C已经执行的UI线程之前,在当前线程上$ C都将完成。

Because the Sub(x) code is being executed on the UI thread by posting a message on the UI thread it is extremely likely that the calling code on the current thread will have completed before the UI thread has been executed.

问题是用Visual Basic中的底层实现以语句。从本质上讲,编译器会为得到在<$ C设置为没有语句一个匿名的局部变量$ C>结束以语句。

The problem is with the underlying implementation of the Visual Basic With statement. In essence, the compiler creates an anonymous local variable for the With statement that gets set to Nothing at the End With statement.

举个例子,如果你有这样的code:

As an example, if you have this code:

Dim p As New Person
With p
    .Name = "James"
    .Age = 40
End With

Visual Basic编译器会变成这样:

The Visual Basic compiler turns this into:

Dim p As New Person
Dim VB$t_ref$L0 As Person = p
VB$t_ref$L0.Name = "James"
VB$t_ref$L0.Age = 40
VB$t_ref$L0 = Nothing

所以,在你的情况下,当UI线程code执行该匿名的局部变量现在是没有,你会得到你的对象引用不设置到例如一个对象的异常。

So, in your case, when the UI thread code is executed this anonymous local variable is now Nothing and you get your "Object reference not set to an instance of an object" exception.

您code本质上是相同的:

Your code essentially is equivalent to this:

Dim VB$t_ref$L0 = Devices
Dim action = new Action(Sub(x) x.Text = VB$t_ref$L0.Miller1.CurrentRead.GetParsedValue.ToString);
VB$t_ref$L0 = Nothing
action(MillerCurrentIndicator);

通过该操作被称为 VB $ t_ref $ L0 变量已被设置为任何时间和whammo!

By the time that the action is called the VB$t_ref$L0 variable is already set to Nothing and whammo!

答案是不使用语句。他们是坏的。

The answer is not to use a With statement. They are bad.

这应该是你的答案,但也有一些其他的问题,在你的code,虽然你应该看看了。

That should be your answer, but there are a number of other issues in your code though that you should look at too.

的SyncLock code使用本地锁定变量基本上呈现锁没用。所以,不这样做:

Your SyncLock code is using a local locking variable which essentially renders the lock useless. So don't do this:

Private Sub UpdateIndicators()
    Dim ObjLock As New Object
    SyncLock ObjLock
    With Devices
        ...
    End With
    End SyncLock
End Sub

做到这一点,而不是:

Do this instead:

Private ObjLock As New Object
Private Sub UpdateIndicators()
    SyncLock ObjLock
    With Devices
        ...
    End With
    End SyncLock
End Sub

所有调用的 InvokeControl UpdateIndicators 方法,因此很难调试code 。减少这些调用单个呼叫的应该可以帮助您极大。试试这个:

All of the calls to InvokeControl in the UpdateIndicators method are making it difficult to debug your code. Reducing all of these calls to a single call should help you immensely. Try this instead:

Private Sub UpdateIndicators()
    SyncLock ObjLock
        InvokeControl(Me, AddressOf UpdateIndicators)
    End SyncLock
End Sub

Private Sub UpdateIndicators(ByVal form As ControlInvokeForm)
    With Devices
        EmergencyStopPictureBox.Toggle(Mode > RunMode.NotRunning)
        MillerCurrentIndicator.Text = .Miller1.CurrentRead.GetParsedValue.ToString
        ...
        ToggleImageList(.GateValvePosition.ImageList, .GateValvePosition.SetValue > 0)
    End With
End Sub

显然,你需要删除随着设备 code,使这些工作。

Obviously you need to remove the With Devices code to make these work.

有许多与以下类型$ C $的c的问题:

There are a number of issues with the following type of code:

If .Ion.GetParsedValue > 0.01 Then
    InvokeControl(IonIndicator, Sub(x) x.Text = "OFF")
Else
    InvokeControl(IonIndicator, Sub(x) x.Text = .Ion.GetParsedValue.ToString)
End If

.Ion.GetParsedValue 的值可能具备的条件进行评估,并正在执行的否则语句之间变化。这是复杂,因为在如果语句中的条件是在当前线程上进行评估,但在执行上的否则语句UI线程因此延迟可能是巨大的。此外,如果 .Ion。类不是线程安全的你暴露自己潜在的错误。

The value of .Ion.GetParsedValue may have changed between the condition being evaluated and the Else statement being executed. This is compounded because the condition on the If statement is evaluated on the current thread, but the Else statement is executed on the UI thread so the delay could be great. Also, if the .Ion. class is not thread-safe you are exposing yourself to potential errors.

做到这一点,而不是:

Dim parsedIonValue = .Ion.GetParsedValue
If parsedIonValue > 0.01 Then
    InvokeControl(IonIndicator, Sub(x) x.Text = "OFF")
Else
    InvokeControl(IonIndicator, Sub(x) x.Text = parsedIonValue.ToString)
End If

(这也摆脱你的的问题。)

使用自动复位= TRUE MasterTimer 来自动调用已启用=假事件触发时,避免竞态条件(远程)的可能性。

Use AutoReset = True on your MasterTimer to automatically call Enabled = False when the event fires to avoid the (remote) possibility of race conditions.

您code似乎也不是在你使用正确的随着设备 UpdateIndicators 方法,但你必须在 ReadFromDevices 方法的对于每个循环。 设备则似乎是一个集合,但在 UpdateIndicators 的code使用设备对象,就好像它是一个设备对象。而且它调用 .SubstrateBiasVoltage 设备对象。所以,我不知道到底是什么物体设备实际上在做什么。

Your code also doesn't seem to be correct in that you are using With Devices in the UpdateIndicators method, but you have a For Each loop in the ReadFromDevices method. Devices then appears to be a collection, but the code in UpdateIndicators is using the Devices object as if it were a Device object. And it is calling .SubstrateBiasVoltage on the Devices object. So I'm not sure exactly what the object Devices is actually doing.

ToggleImageList 方法,则需要传递的ImageList 参数被传递的ByRef ,但你不改变参照的ImageList 。这是更好,然后把它传递 BYVAL ,以避免潜在的bug。

In the ToggleImageList method you are passing the ImageList parameter is being passed ByRef but you are not changing the reference to the ImageList. It's better then to pass it in ByVal to avoid potential bugs.

此外,而不是做这样的:

Also, rather than doing this:

If dev.GetType.Equals(GetType(Miller)) Then
    Dim devAsMiller As Miller = CType(dev, Miller)
    With devAsMiller

这将是清洁要做到这一点:

It would be cleaner to do this:

Dim devAsMiller = TryCast(dev, Miller)
If devAsMiller IsNot Nothing Then
    With devAsMiller

我希望这似乎并不像我那颗在开机!希望这是很有帮助的。

I hope this doesn't seem like I'm sinking the boot in! Hopefully it is helpful.

这篇关于VB.NET试图修改一个通用的Invoke方法到一个通用的BeginInvoke方法,有意想不到的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆