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