Сложно использовать отладку, чтобы перехватить исключение, так как оно будет происходить при любом из нескольких PostMessage
вызовов к насосу сообщений пользовательского интерфейса (вызванных InvokeControl
и BeginInvoke
вызовами).Visual Studio будет трудно сломать исключение.Возможно, именно поэтому создается впечатление, что исключение «магически» генерируется.
Ваша проблема заключается не в реализации InvokeControl
, а в методе UpdateIndicators
.Это происходит из-за использования оператора With
и асинхронных вызовов потока пользовательского интерфейса, например:
With Devices
...
InvokeControl(MillerCurrentIndicator, Sub(x) x.Text = .Miller1.CurrentRead.GetParsedValue.ToString)
...
End With
Поскольку код Sub(x)
выполняется в потоке пользовательского интерфейса путем отправки сообщения в пользовательский интерфейсВ потоке очень вероятно, что вызывающий код в текущем потоке завершится до того, как будет выполнен поток пользовательского интерфейса.
Проблема заключается в базовой реализации оператора 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
Итак, в вашем случае, когда исполняется код потока пользовательского интерфейса, эта анонимная локальная переменная теперь 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
и whammo!
Ответ не должен использовать оператор 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
выполняется в потоке пользовательского интерфейса, поэтому задержка может быть большой.Кроме того, если класс .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 Devices
в методе UpdateIndicators
, но у вас есть цикл For Each
в методе ReadFromDevices
.Тогда Devices
представляется коллекцией, но код в UpdateIndicators
использует объект Devices
, как если бы он был Device
.И это вызывает .SubstrateBiasVoltage
на Devices
объекте.Поэтому я не уверен, что именно делает объект 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
Надеюсь, это не похоже на то, как будто я погрузил ботинок!Надеюсь, это полезно.