gpt4 book ai didi

VB.NET 尝试将泛型 Invoke 方法修改为泛型 BeginInvoke 方法,出现意外问题

转载 作者:行者123 更新时间:2023-12-02 05:41:25 25 4
gpt4 key购买 nike

VB.NET 2010、.NET 4

你好,

我一直在使用一个非常漂亮的通用调用方法来从后台线程更新 UI。我忘记了从哪里复制它(从 C# 将其转换为 VB.NET),但它是:

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

现在,我想修改它以创建一个函数,如果不需要调用(或引发异常)则返回 Nothing 或如果需要调用则从 BeginInvoke 返回 IAsyncResult。这是我所拥有的:
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

我想这样做主要是为了避免阻塞。问题是我现在在调用这样的电话时遇到错误:
InvokeControl(SomeTextBox, Sub(x) x.Text = "Some text")

这适用于原始 Invoke(而不是 BeginInvoke)方法。现在我得到一个“对象引用未设置为对象的实例”异常。如果我 Handlebars 表放在 SomeTextBox 上,它会说
SomeTextBox {Text = (Text) threw an exception of type Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException.}

此类 InvokeControl 调用来自 System.Timers.Timer 的 Elapsed 事件可能是相关的。它的时间间隔是 500 毫秒,对于 UI 更新完成(如果重要的话)应该足够长。到底是怎么回事?

在此先感谢您的帮助!

编辑:更多细节

这是我的 System.Timer.Timer 的 Elapsed 处理程序:
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,但希望它能帮助找出问题所在。

此外,在其中一个文本框和一些断点上使用 watch 时,我发现错误是在 ReadFromDevices 之后但在 UpdateIndicators 之前以某种方式神奇地抛出的。我的意思是,ReadFromDevices 末尾的断点显示文本框没有引发错误,但 UpdateIndicators 开头的断点(在进行任何 InvokeControl 调用之前)表明它们已经......

最佳答案

很难使用调试来捕获异常,因为它会发生在多个 PostMessage 中的任何一个上。对 UI 消息泵的调用(由 InvokeControlBeginInvoke 调用引起)。 Visual Studio 将很难打破异常。这可能就是为什么看起来异常“神奇地”被抛出的原因。

您的问题不在于您对 InvokeControl 的实现。但在 UpdateIndicators方法。这是因为使用了With语句和异步 UI 线程调用如下:

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

因为 Sub(x)代码正在通过在 UI 线程上发布消息在 UI 线程上执行,当前线程上的调用代码极有可能在 UI 线程执行之前已经完成。

问题在于 Visual Basic 的底层实现 With陈述。实质上,编译器为 With 创建了一个匿名局部变量。设置为 Nothing 的语句在 End With陈述。

例如,如果您有以下代码:
Dim p As New Person
With p
.Name = "James"
.Age = 40
End With

Visual Basic 编译器将其转换为:
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 线程代码时,这个匿名局部变量现在是 Nothing并且您得到“对象引用未设置为对象的实例”异常。

您的代码基本上等同于:
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 时变量已设置为 Nothing哇!

答案是不要使用 With陈述。他们很坏。

这应该是您的答案,但是您的代码中还有许多其他问题,尽管您也应该查看这些问题。

您的 SyncLock代码使用了一个局部锁定变量,这实际上使锁定变得无用。所以不要这样做:
Private Sub UpdateIndicators()
Dim ObjLock As New Object
SyncLock ObjLock
With Devices
...
End With
End SyncLock
End Sub

改为这样做:
Private ObjLock As New Object
Private Sub UpdateIndicators()
SyncLock ObjLock
With Devices
...
End With
End SyncLock
End Sub

所有对 InvokeControl 的调用在 UpdateIndicators方法使调试代码变得困难。将所有这些调用减少到一个调用应该会极大地帮助您。试试这个:
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

显然你需要删除 With Devices使这些工作的代码。

以下类型的代码存在许多问题:
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 的值在被评估的条件和 Else 之间可能发生了变化。正在执行的语句。这很复杂,因为 If 上的条件语句在当前线程上计算,但 Else语句在 UI 线程上执行,因此延迟可能很大。此外,如果 .Ion.类不是线程安全的,您将自己暴露在潜在的错误中。

改为这样做:
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

(这也消除了您的 With 问题。)

使用 AutoReset = True在您的 MasterTimer 上自动调用 Enabled = False当事件触发以避免竞争条件的(远程)可能性时。

您的代码似乎也不正确,因为您使用的是 With DevicesUpdateIndicators方法,但你有一个 For EachReadFromDevices 中循环方法。 Devices then 似乎是一个集合,但 UpdateIndicators 中的代码正在使用 Devices对象好像是 Device目的。它正在调用 .SubstrateBiasVoltageDevices目的。所以我不确定对象 Devices 到底是什么实际上是在做。

ToggleImageList您传递的方法 ImageList正在传递参数 ByRef但您没有更改对 ImageList 的引用.最好在 ByVal 中传递以避免潜在的错误。

此外,而不是这样做:
If dev.GetType.Equals(GetType(Miller)) Then
Dim devAsMiller As Miller = CType(dev, Miller)
With devAsMiller

这样做会更干净:
Dim devAsMiller = TryCast(dev, Miller)
If devAsMiller IsNot Nothing Then
With devAsMiller

我希望这看起来不像是我把 Boot 陷进去了!希望它是有帮助的。

关于VB.NET 尝试将泛型 Invoke 方法修改为泛型 BeginInvoke 方法,出现意外问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3886187/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com