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调用之前)显示它们有...
答案 0 :(得分:4)
很难使用调试来捕获异常,因为它会发生在对UI消息泵的多个PostMessage
调用中的任何一个上(由InvokeControl
和BeginInvoke
调用引起)。 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
语句。