multithreading - VB.NET Trying to modify a generic Invoke method to a generic BeginInvoke method, having unexpected problems -
vb.net 2010, .net 4
hello,
i have been using pretty slick generic invoke method ui updating background threads. forget copied (converted vb.net c#), here is:
public sub invokecontrol(of t control)(byval control t, byval action action(of t)) if control.invokerequired try control.invoke(new action(of t, action(of t))(addressof invokecontrol), new object() {control, action}) catch ex exception end try else action(control) end if end sub now, want modify make function returns nothing if no invoke required (or exception thrown) or iasyncresult returned begininvoke if invoke required. here's have:
public function invokecontrol(of t control)(byval control t, byval action action(of t)) iasyncresult if control.invokerequired try return control.begininvoke(new action(of t, action(of t))(addressof invokecontrol), new object() {control, action}) catch ex exception return nothing end try else action(control) return nothing end if end function i wanted avoid blocking. problem errors when making calls such this:
invokecontrol(sometextbox, sub(x) x.text = "some text") this worked fine original invoke (rather begininvoke) method. "object reference not set instance of object" exception. if put watch on sometextbox, says
sometextbox {text = (text) threw exception of type microsoft.visualstudio.debugger.runtime.crossthreadmessagingexception.} it might relevant such invokecontrol calls come within system.timers.timer's elapsed event. interval 500ms, should more long enough ui updates complete (if matters). going on?
thanks in advance help!
edit: more details
here system.timer.timer's elapsed handler:
private sub mastertimer_elapsed(byval sender object, byval e system.timers.elapsedeventargs) handles mastertimer.elapsed mastertimer.enabled = false if not mastertimer.interval = my.settings.timingmastertimerinterval mastertimer.interval = my.settings.timingmastertimerinterval neweventlogentry("the master timer's interval has been changed " & mastertimer.interval.tostring & " milliseconds.") end if invokecontrol(timerpicturebox, sub(x) x.toggle(true)) readfromdevices() updateindicators() 'this block not executing when error thrown if mode > runmode.notrunning updateprocesstime() updateremainingtime() updatestatustime() end if 'this block not executing when error thrown if mode = runmode.running checkmillercurrent() checktolerances() end if mastertimer.enabled = true end sub private sub readfromdevices() each dev device in devices try if dev.gettype.equals(gettype(miller)) dim devasmiller miller = ctype(dev, miller) devasmiller if .poweron.enabled .poweron.read() if .currentread.enabled .currentread.read() if .voltageread.enabled .voltageread.read() if .trigger.enabled .trigger.read() if .shutter.enabled .shutter.read() end elseif dev.gettype.equals(gettype(substratebiasvoltage)) dim devassubstratebiasvoltage substratebiasvoltage = ctype(dev, substratebiasvoltage) devassubstratebiasvoltage if .lambdacurrentread.enabled .lambdacurrentread.read() if .lambdavoltageread.enabled .lambdavoltageread.read() if .biasresistor.enabled .biasresistor.read() if .pinnacle.enabled .pinnacle.read() end else if dev.enabled dev.read() end if catch ex exception neweventlogentry("an error occurred while trying read device.", ex, eventlogitem.types.warning) end try next end sub private sub updateindicators() dim objlock new object synclock objlock devices invokecontrol(emergencystoppicturebox, sub(x 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) .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 invokecontrol(heaterpowerindicator, sub(x) x.text = .heaterpower.getparsedvalue.tostring) invokecontrol(convectronindicator, sub(x) x.text = .convectron.getparsedvalue.tostring) if .baratron.getparsedvalue > 200 invokecontrol(baratronindicator, sub(x) x.text = "off") else invokecontrol(baratronindicator, sub(x) x.text = .baratron.getparsedvalue.tostring) end if if .ion.getparsedvalue > 0.01 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 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) .substratebiasvoltage toggleimagelist(.lambdavoltageread.imagelist, .lambdavoltageread.getparsedvalue > 0 , .biasresistor.getparsedvalue = biasresistor.lambda) toggleimagelist(.pinnacle.imagelist, .pinnacle.getparsedvalue > 10 , .biasresistor.getparsedvalue = biasresistor.pinnacle) end 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 , .watermonitor2.value = power.on) toggleimagelist(.gatevalveposition.imagelist, .gatevalveposition.setvalue > 0) end end synclock end sub private sub toggleimagelist(byref imagelist imagelist, byval ison boolean) each img onoffpicturebox in imagelist safeinvokecontrol(img, sub(x onoffpicturebox) x.toggle(ison)) next end sub i hope that's not tmi, it'll spot going wrong.
also, watch on 1 of textboxes , breakpoints, found error somehow magically thrown after readfromdevices before updateindicators. that, mean breakpoint @ end of readfromdevices shows textboxes haven't thrown error, breakpoint @ beginning of updateindicators (before invokecontrol calls have been made) shows have...
it difficult use debugging catch exception occur on 1 of multiple postmessage calls ui message pump (caused invokecontrol & begininvoke calls). visual studio have hard time breaking on exception. possibly why appears exception "magically" being thrown.
your issue lies not in implementation of invokecontrol in updateindicators method. because of use of with statement , asynchronous ui thread calls this:
with devices ... invokecontrol(millercurrentindicator, sub(x) x.text = .miller1.currentread.getparsedvalue.tostring) ... end because sub(x) code being executed on ui thread posting message on ui thread extremely calling code on current thread have completed before ui thread has been executed.
the problem underlying implementation of visual basic with statement. in essence, compiler creates anonymous local variable with statement gets set nothing @ end with statement.
as example, if have code:
dim p new person p .name = "james" .age = 40 end the visual basic compiler turns into:
dim p new person dim vb$t_ref$l0 person = p vb$t_ref$l0.name = "james" vb$t_ref$l0.age = 40 vb$t_ref$l0 = nothing so, in case, when ui thread code executed anonymous local variable nothing , "object reference not set instance of object" exception.
your code equivalent 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); by time action called vb$t_ref$l0 variable set nothing , whammo!
the answer not use with statement. bad.
that should answer, there number of other issues in code though should @ too.
your synclock code using local locking variable renders lock useless. don't this:
private sub updateindicators() dim objlock new object synclock objlock devices ... end end synclock end sub do instead:
private objlock new object private sub updateindicators() synclock objlock devices ... end end synclock end sub all of calls invokecontrol in updateindicators method making difficult debug code. reducing of these calls single call should immensely. try instead:
private sub updateindicators() synclock objlock invokecontrol(me, addressof updateindicators) end synclock end sub private sub updateindicators(byval form controlinvokeform) devices emergencystoppicturebox.toggle(mode > runmode.notrunning) millercurrentindicator.text = .miller1.currentread.getparsedvalue.tostring ... toggleimagelist(.gatevalveposition.imagelist, .gatevalveposition.setvalue > 0) end end sub obviously need remove with devices code make these work.
there number of issues following type of code:
if .ion.getparsedvalue > 0.01 invokecontrol(ionindicator, sub(x) x.text = "off") else invokecontrol(ionindicator, sub(x) x.text = .ion.getparsedvalue.tostring) end if the value of .ion.getparsedvalue may have changed between condition being evaluated , else statement being executed. compounded because condition on if statement evaluated on current thread, else statement executed on ui thread delay great. also, if .ion. class not thread-safe exposing potential errors.
do instead:
dim parsedionvalue = .ion.getparsedvalue if parsedionvalue > 0.01 invokecontrol(ionindicator, sub(x) x.text = "off") else invokecontrol(ionindicator, sub(x) x.text = parsedionvalue.tostring) end if (this gets rid of with issue.)
use autoreset = true on mastertimer automatically call enabled = false when event fires avoid (remote) possibility of race conditions.
your code doesn't seem correct in using with devices in updateindicators method, have for each loop in readfromdevices method. devices appears collection, code in updateindicators using devices object if device object. , calling .substratebiasvoltage on devices object. i'm not sure object devices doing.
in toggleimagelist method passing imagelist parameter being passed byref not changing reference imagelist. it's better pass in byval avoid potential bugs.
also, rather doing this:
if dev.gettype.equals(gettype(miller)) dim devasmiller miller = ctype(dev, miller) devasmiller it cleaner this:
dim devasmiller = trycast(dev, miller) if devasmiller isnot nothing devasmiller i hope doesn't seem i'm sinking boot in! helpful.
Comments
Post a Comment