VB.NET尝试将通用的Invoke方法修改为通用的BeginInvoke方法,遇到意外问题

时间:2010-10-07 22:01:56

标签: vb.net multithreading asynchronous timer marshalling

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)方法。现在我得到一个“对象引用没有设置为对象的实例”异常。如果我把手表放在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,但希望它能帮助发现出错的地方。

此外,通过其中一个文本框和一些断点上的监视,我发现错误在 ReadFromDevices之后以某种方式神奇地抛出了,而在 UpdateIndicators之前。通过这个,我的意思是在ReadFromDevices的最后一个断点显示文本框没有抛出错误,但UpdateIndicators开头的断点(在任何InvokeControl调用之前)显示它们有...

2 个答案:

答案 0 :(得分:4)

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

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

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

因为在UI线程上通过在UI线程上发布消息来执行Sub(x)代码,所以当前线程上的调用代码很可能在UI线程被执行之前完成。 / p>

问题在于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并且已经变为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语句在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 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

这样做会更干净:

{{1}}

我希望这似乎不像我正在沉没!希望它有用。

答案 1 :(得分:0)

Enigmativity已经清楚地解决了你的整个问题,但是只能继续使用 public virtual ActionResult Index() { SetupViewer(); if (_bucketFound) { //upload file RestRequest uploadReq = new RestRequest(); uploadReq.Resource = "oss/v2/buckets/"+_bucketName+"/objects/"+_filepath; uploadReq.Method = Method.PUT; uploadReq.AddHeader("Content-Type", _fileContentType); uploadReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); var result= _client.Execute(uploadReq); if (result.StatusCode == System.Net.HttpStatusCode.OK) { String responseString = result.Content; int len = responseString.Length; int objectKeyIndex = responseString.IndexOf("\"objectKey\" : \""); int index = responseString.IndexOf("urn:"); responseString = responseString.Substring(index, objectKeyIndex - index-5).Replace("urn:", "").Trim(); _fileUrn = "urn:"+responseString; //convert urn to base64 string base64Urn = Base64Convertor.Base64Encode(_fileUrn); // Remove ending '=' signs // Use _ instead of / // Use - insteaqd of + base64Urn = base64Urn.Replace("=", ""); //translate to SVF format //RestRequest svfReq = new RestRequest(); //svfReq.Resource = "modelderivative/v2/designdata/job"; //svfReq.Method = Method.POST; //svfReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); //svfReq.AddParameter("Content-Type", "application/json;charset=utf-8", ParameterType.HttpHeader); //string body = "{\"input\":{\"urn\":\"" + base64Urn + "\",\"compressedUrn\":true,\"rootFilename\":\""+_filepath+ "\"},\"output\":{\"formats\":[{\"type\":\"svf\",\"views\":[\"2d\"]}]}}"; //svfReq.AddParameter("application/json", body, ParameterType.RequestBody); // translate to OBJ format RestRequest objReq = new RestRequest(); objReq.Resource = "modelderivative/v2/designdata/job"; objReq.Method = Method.POST; objReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); objReq.AddParameter("Content-Type", "application/json", ParameterType.HttpHeader); string body = "{\"input\":{\"urn\":\"" + base64Urn + "\"},\"output\":{\"formats\":[{\"type\":\"obj\"}]}}"; result = _client.Execute(objReq); if (result.StatusCode == System.Net.HttpStatusCode.OK || result.StatusCode==System.Net.HttpStatusCode.Created) { //check the transition complete RestRequest checkTransitionCompleteGetReq = new RestRequest(); checkTransitionCompleteGetReq.Resource = "modelderivative/v2/designdata/"+base64Urn+"/manifest"; checkTransitionCompleteGetReq.Method = Method.GET; checkTransitionCompleteGetReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); var result2 = _client.Execute(checkTransitionCompleteGetReq); if (result2.StatusCode == System.Net.HttpStatusCode.OK) { ViewBag.BucketFound = result.Content; } } } } return View(); } void SetupViewer() { // Authentication bool authenticationDone = false; RestRequest authReq = new RestRequest(); authReq.Resource = "authentication/v1/authenticate"; authReq.Method = Method.POST; authReq.AddHeader("Content-Type", "application/x-www-form-urlencoded"); authReq.AddParameter("client_id", ConfigurationManager.AppSettings["ClientId"]); authReq.AddParameter("client_secret", ConfigurationManager.AppSettings["ClientSecret"]); authReq.AddParameter("grant_type", "client_credentials"); authReq.AddParameter("scope", "bucket:create bucket:read data:write data:read"); IRestResponse result = _client.Execute(authReq); if (result.StatusCode == System.Net.HttpStatusCode.OK) { String responseString = result.Content; int len = responseString.Length; int index = responseString.IndexOf("\"access_token\":\"") + "\"access_token\":\"".Length; responseString = responseString.Substring(index, len - index - 1); int index2 = responseString.IndexOf("\""); _accessToken = responseString.Substring(0, index2); //Set the token. RestRequest setTokenReq = new RestRequest(); setTokenReq.Resource = "utility/v1/settoken"; setTokenReq.Method = Method.POST; setTokenReq.AddHeader("Content-Type", "application/x-www-form-urlencoded"); setTokenReq.AddParameter("access-token", _accessToken); IRestResponse resp = _client.Execute(setTokenReq); if (resp.StatusCode == System.Net.HttpStatusCode.OK) { authenticationDone = true; } } if (!authenticationDone) { ViewData["Message"] = "View and Data client authentication failed !"; _accessToken = String.Empty; return; } RestRequest bucketReq = new RestRequest(); bucketReq.Resource = "oss/v2/buckets"; bucketReq.Method = Method.POST; bucketReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); bucketReq.AddParameter("Content-Type", "application/json", ParameterType.HttpHeader); //bucketname is the name of the bucket. string body = "{\"bucketKey\":\"" + _bucketName + "\",\"policyKey\":\"transient\"}"; bucketReq.AddParameter("application/json", body, ParameterType.RequestBody); result = _client.Execute(bucketReq); if (result.StatusCode == System.Net.HttpStatusCode.Conflict || result.StatusCode == System.Net.HttpStatusCode.OK) { // _bucketFound = true; //Check bucket RestRequest bucketGetReq = new RestRequest(); bucketGetReq.Resource = "oss/v2/buckets"; bucketGetReq.Method = Method.GET; bucketGetReq.AddParameter("Authorization", "Bearer " + _accessToken, ParameterType.HttpHeader); bucketGetReq.AddParameter("Content-Type", "application/json", ParameterType.HttpHeader); result = _client.Execute(bucketGetReq); if (result.StatusCode == System.Net.HttpStatusCode.OK) { _bucketFound = true; ViewBag.BucketFound = "Found"; } else ViewBag.BucketFound = "NotFound"; } else { ViewData["Message"] = "View and Data bucket could not be accessed !"; _bucketFound = false; return; } } 语句:(即我绝对不是说它是你整个程序的正确解决方案,只要强调,一旦你知道问题有With语句问题的解决方案。)

就像我们以前必须使用With循环变量一样,为for表达式声明一个局部变量可以避免这个问题:

.

当然,这通常只是当属性返回“活动”对象时所需的内容,该对象将在目标线程调用时进行更新。如果不是这种情况,则必须删除With Devices ... Dim miller1 = .Miller1 InvokeControl(MillerCurrentIndicator, Sub(x) x.Text = miller1.CurrentRead.GetParsedValue.ToString) ... End With 语句。