c# - WebBrowser Control Slideshow Hanging after Running for Extended Time -
i'm new developer , 1 has me stumped.
my winforms application slideshow websites rotates through list of urls, fading-in/out on each transition using second form "curtain". it's meant run indefinite period of time consistently hangs on transition after running couple of days.
form1:
httpwebresponse response = null; list<slide.doc> slist = null; bool repeatslideshow = true; bool pageloaded = false; double curtainanimstep = 0.05; int errorcount = 0; public form1() { initializecomponent(); cursorshown = false; this.visible = true; this.formborderstyle = formborderstyle.none; this.windowstate = formwindowstate.maximized; webbrowser1.scrollbarsenabled = false; webbrowser1.scripterrorssuppressed = true; slideshow(environment, channel); } public void slideshow(string environment, string channel) { while (repeatslideshow) { try { slist = slide.convertjsontoslide(slide.getparams(environment, channel)); } catch (exception) { form2 curtain = new form2(curtainanimstep); curtain.show(); waitforfade(curtain, 1); displayerror(); raisecurtain(curtain, curtainanimstep); waitforfade(curtain, 0); curtain.dispose(); waitaround(30); continue; } foreach (slide.doc s in slist) { bool slidewasdisplayed = false; form2 curtain = new form2(curtainanimstep); curtain.show(); waitforfade(curtain, 1); slidewasdisplayed = displayslide(s.url_text); if (slidewasdisplayed == false) { webbrowser1.documenttext = "<html><body style='background-color: #1c1c1c;'></body></html>"; redrawpage(); } raisecurtain(curtain, curtainanimstep); waitforfade(curtain, 0); curtain.dispose(); if (slidewasdisplayed == true) { waitaround(s.display_sec); } } if (errorcount == slist.count) { form2 curtain = new form2(curtainanimstep); curtain.show(); waitforfade(curtain, 1); displayerror(); raisecurtain(curtain, curtainanimstep); waitforfade(curtain, 0); curtain.dispose(); waitaround(30); } errorcount = 0; utilities.web.webbrowserhelper.webbrowserhelper.clearcache(); } } public bool displayslide(string slideurl) { httpwebrequest request = (httpwebrequest)webrequest.create(slideurl); request.timeout = 1000; try { response = (httpwebresponse)request.getresponse(); webbrowser1.navigate(slideurl); redrawpage(); response.dispose(); return true; } catch (webexception) { errorcount++; return false; } } public void redrawpage() { while (pageloaded == false) { application.doevents(); } webbrowser1.invalidate(); application.doevents(); pageloaded = false; } public void raisecurtain(form curtain, double curtainanimstep) { while (curtain.opacity > 0) { curtain.opacity -= curtainanimstep; application.doevents(); system.threading.thread.sleep(10); // how long between shifts in opacity (not interval between slides) } } public void waitaround(int duration) { datetime dt2 = datetime.now; while (dt2.addseconds(duration) > datetime.now) { application.doevents(); } } public void waitforfade(form curtain, int finalopacity) { while (curtain.opacity != finalopacity) { datetime dt = datetime.now; dt = dt.addseconds(1); while (dt > datetime.now) { application.doevents(); } } } private void webbrowser1_documentcompleted(object sender, webbrowserdocumentcompletedeventargs e) { pageloaded = true; }
form2:
public form2(double animstep) { initializecomponent(); this.animstep = animstep; } public double animstep { get; set; } private async void form2_load(object sender, eventargs e) { while (opacity < 1.0) { await task.delay(10); opacity += animstep; } opacity = 1; }
i've been working on long time, have admit genuinely don't know should looking @ point.
could use of application.doevents
responsible? leaving them out breaks application, can't figure out alternative appproach.
looking @ code (and indicated noseratio) 1 of things advice rid of need doevents
calls. remember in windows there dedicated ui thread used update controls on form. doing lot of stuff (in loops, calling bunch of methods) on same ui thread windows controls depends on cooperation share time them, hence calls doevents
.
i'm going use backgroundworker
, timer
, waithandle
schedule commands update ui background thread. little needed on ui thread.
form load
form1 have webbrowsercontrol , backgroundworker. queue hold commands needs executed. load event start backgroundworker.
form2 frm2 = new form2(); queue<icommandexecutor> commands = new queue<icommandexecutor>(); private void form1_load(object sender, eventargs e) { frm2.show(); frm2.bringtofront(); commands.enqueue(new loadslideshow(this, frm2, commands)); backgroundworker1.runworkerasync(); }
backgroundworker
the backgroundworker dowork
event engine runs on it's own background thread. runs long there commands found in queue. after fetching command it's execute
method fired. if command supports disposing dispose
method called , command processed , start on again.
private void backgroundworker1_dowork(object sender, doworkeventargs e) { while(commands.count>0) { icommandexecutor cmd = commands.dequeue(); try { cmd.execute(); // dispose if can idisposable sync = cmd idisposable; if (sync != null) { sync.dispose(); } } catch(exception exp) { // add commands here trace.writeline("error" + exp.message); } } }
commands
there standard interface available implement command pattern. icommandexecutor
has single method, execute
. can create different classes implement interface. each class holds own state , references , can simple timer of complex loading new batch of urls show.
public class showslide:icommandexecutor { string url; form1 form; autoresetevent done = new autoresetevent(false); public showslide(form1 form, string url) { this.url = url; this.form = form; } public void execute() { // if not on ui thread... if (form.invokerequired) { // ... switch it... form.invoke(new methodinvoker(execute)); } else { // .. on ui thread // reused code form.displayslide(url); } } }
here timer. notice how timer class used , timerdone
waithandle make backgroundthread continue work if timer has finished when dispose
called.
public class waitforseconds: icommandexecutor, idisposable { int ms; system.threading.timer timer; manualresetevent timerdone = new manualresetevent(false); public waitforseconds(int secs) { this.ms = secs * 1000; } public void execute() { // use timer timer = new system.threading.timer( (state) => timerdone.set() // signal done ); timerdone.reset(); timer.change(this.ms, timeout.infinite); } public void dispose() { timerdone.waitone(); timerdone.dispose(); timer.dispose(); } }
to setup commands in correct order use following command class implememntation takes command queue, form1 , form2 parameters on constructor. execute
command loads url's fed webbrowser control. each url adds commands needs executed queue. @ end this
instance added queue means class used again if commands have been processed. queue there never empty.
public class loadslideshow: icommandexecutor { readonly queue<icommandexecutor> commands; readonly form1 form; readonly form2 form2; public loadslideshow(form1 form, form2 form2, queue<icommandexecutor> cmds) { this.form = form; commands = cmds; this.form2 = form2; } public void execute() { var list = slide.convertjsontoslide(null); foreach (var slide in list) { commands.enqueue(new showslide(form, slide.url_text)); commands.enqueue(new waitforseconds(1)); //commands.enqueue(new lowercurtain(form2)); commands.enqueue(new waitforseconds(slide.display_sec)); //commands.enqueue(new raisecurtain(form2)); } commands.enqueue(this); } }
this there is needed basic slideshow going.
for called curtain going similar form2 i'll use backgroundworker_progress event well.
form2 curtain
form2 act curtain changing it's opacity
in loop. has it's own backgroundworker:
manualresetevent statechange = new manualresetevent(false); public manualresetevent statechangedone = new manualresetevent(false); private void form2_load(object sender, eventargs e) { backgroundworker1.runworkerasync(); } private void backgroundworker1_dowork(object sender, doworkeventargs e) { while(statechange.waitone()) { statechange.reset(); var progressdone = new autoresetevent(false); int progress = 0; using(var timer = new system.threading.timer(_=> { backgroundworker1.reportprogress(progress); progress += 2; if (progress>=100) { progressdone.set(); } }, null, 0, 25)) { progressdone.waitone(); } statechangedone.set(); } }
the background worker calls resportprogress int indicating prpgress. causes progresschanged event raised. based on state curtain needs in, calculate correct value opacity
.
private void backgroundworker1_progresschanged(object sender, progresschangedeventargs e) { switch(state) { case curtain.up: this.opacity = e.progresspercentage / 100.0; break; case curtain.down: this.opacity = (100 - e.progresspercentage) / 100.0; break; } }
to started create 2 public methods called , down:
enum curtain { up, down } curtain state; public void up() { state = curtain.up; statechange.set(); statechangedone.reset(); } public void down() { state = curtain.down; statechange.set(); statechangedone.reset(); }
with left implementation of command classes added command queue , handled background worker of form1:
public class raisecurtain:icommandexecutor, idisposable { readonly form2 form2; public raisecurtain( form2 form2) { this.form2 = form2; } public void execute() { if (form2.invokerequired) { form2.invoke(new methodinvoker(execute)); } else { form2.bringtofront(); form2.up(); } } public void dispose() { form2.statechangedone.waitone(); } } public class lowercurtain : icommandexecutor,idisposable { readonly form2 form2; public lowercurtain(form2 form2) { this.form2 = form2; } public void execute() { if (form2.invokerequired) { form2.invoke(new methodinvoker(execute)); } else { form2.down(); } } public void dispose() { form2.statechangedone.waitone(); } }
that it. have eliminated use of doevents.
there 1 caveat: doesn't guarantee application stop again after couple of hours/days. reason possible memory-leak in webbrowser control , in testing did see same effect, steadily increasing private memory consumption while managed memory bytes stayed virtually same.
as none of posts provided definitive answer 1 option restart app indicates in 1 of answers here. on plus side, can implement command class...
Comments
Post a Comment