WebBrowser Control Slideshow Hanging after Running for Extended Time - c#

I'm a fairly new developer and this one has me stumped.
My WinForms application is a slideshow for websites that rotates through a list of URLs, fading-in/out on each transition by using a second form as a "curtain". It's meant to run for an indefinite period of time but consistently hangs on the transition after running for a 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 this for a long time, but I have to admit that I genuinely don't even know what I should be looking for at this point.
Could the use of Application.DoEvents be responsible? Leaving them out breaks the application, but I can't figure out an alternative appproach.

Looking at your code (and as indicated by Noseratio) one of the things I advice is to get rid of the need for the DoEvents calls. Just remember that in Windows there is a dedicated UI thread that is used to update the controls on the form. As you are doing a lot of stuff (in loops, calling a bunch of methods) on that same UI thread the Windows controls depends on your cooperation to share some time with them, hence the calls to DoEvents.
I'm going to use a BackgroundWorker and a Timer and WaitHandle to schedule commands that will update the UI from a background thread. With that we do as little as needed on the UI thread.
Form Load
Form1 will only have a webbrowsercontrol and a backgroundworker. A queue will hold the commands that needs to be executed. From the Load event we start the 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 is the engine that runs on it's own background thread. It runs as long as there are commands found in the queue. After fetching a command it's Execute method is fired. If the command supports disposing the Dispose method is called and with that a command is processed and we start over again.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while(commands.Count>0)
{
ICommandExecutor cmd = commands.Dequeue();
try
{
cmd.Execute();
// dispose if we can
IDisposable sync = cmd as IDisposable;
if (sync != null)
{
sync.Dispose();
}
}
catch(Exception exp)
{
// add commands here
Trace.WriteLine("error" + exp.Message);
}
}
}
Commands
There is a standard interface available to implement a command pattern. ICommandExecutor has a single method, Execute. We can create different classes that implement this interface. Each class holds its own state and references and it can be as simple as a timer of as complex as loading a new batch of urls to 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 we are not on the UI thread...
if (form.InvokeRequired)
{
// ... switch to it...
form.Invoke(new MethodInvoker(Execute));
}
else
{
// .. we are on the UI thread now
// reused from your code
form.displaySlide(url);
}
}
}
Here is a timer. Notice how a Timer class is used and the timerDone waithandle to make the backgroundthread continue work only if the timer has finished when Dispose is 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 a timer
timer = new System.Threading.Timer(
(state) => timerDone.Set() // signal we are done
);
timerDone.Reset();
timer.Change(this.ms, Timeout.Infinite);
}
public void Dispose()
{
timerDone.WaitOne();
timerDone.Dispose();
timer.Dispose();
}
}
To setup the commands in the correct order we use the following command class implememntation that takes the Command queue, Form1 and Form2 as parameters on its constructor. The Execute command loads all url's to be fed to the webbrowser control. For each url it adds the commands that needs to be executed to the queue. At the end the this instance is added to the queue as well which means the class will be used again if all commands have been processed. The queue will there for never be 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 is basically all there is that is needed to get a basic slideshow going.
For the so called curtain we are going to do something similar with Form2 but I'll use the BackgroundWorker_progress event as well.
Form2 the Curtain
Form2 will act as the curtain by changing it's Opacity in a loop. It 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 with an int indicating its prpgress. That causes the ProgressChanged event to be raised. Based on what state the Curtain needs to be in, we calculate the correct value for the 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 get this all started we create two public methods called Up and 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 that we are only left with the implementation of the Command classes that will be added to the Command queue and handled by the 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 is it. We have eliminated the use of DoEvents.
There is one caveat: this doesn't guarantee that the application will stop again after a couple of hours/days. The reason for this is a possible memory-leak in the webbrowser control and in my testing I did see the same effect, a slowly but steadily increasing private memory consumption while the managed memory bytes stayed virtually the same.
As none of the posts provided a definitive answer one option could be to restart your app as indicates in one of the answers here. On the plus side, you can implement this now as a Command class...

Related

C# Form Controls Won't Update with Multithreading

I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.
I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:
Here's the 'main' program:
class Program
{
static MyForm form;
static void Main(string[] args)
{
form = new MyForm();
form.Show();
doWork();
form.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
while (form.Value < 100000)
{
form.ChangeVal();
Thread.Sleep(1);
}
return;
}
}
...And here's the Form. I'm not including the auto-generated stuff from VS.
public partial class MyForm : Form
{
private int val;
public int Value
{
get { return val; }
set { val = value; }
}
public Thread GUIupdater;
public MyForm()
{
InitializeComponent();
this.Refresh();
}
private void MyForm_Load(object sender, EventArgs e)
{
GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
GUIupdater.Start();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
}
public void ChangeVal()
{
val++;
}
private void changeLabel(string s)
{
label.Text = s;
label.Refresh();
}
private delegate void labelChanger(string s);
private void GUIupdaterThread()
{
while (true)
{
Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
Thread.Sleep(100); //100 ms
}
}
private void killThreadOnClose(object sender, FormClosingEventArgs e)
{
GUIupdater.Abort();
}
}
So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!
Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here
Edits:
When I add Console.WriteLine Statements, I discovered that the GUIupdaterThread (the thing that's meant to update the GUI) loop always 'breaks' on the Invoke statement, never reaching 'Thread.Sleep'. I changed it to 'BeginInvoke', which causes the loop to function properly, but this hasn't changed the fact that the GUI doesn't update.
CLARIFICATIONS:
About my 'actual' project:
The main thread here in 'Program' simulates my software, which is a plugin implementing an interface. My decision to alter val / value in that thread, not in the thread created by the window, was deliberate.
I'm constrained to using .NET 4.0 . any more recent features can't help me
Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.
There are several approaches how to update controls from other threads.
I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).
I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.
Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:
This form has a single label on it.
public partial class ProgressForm : Form
{
private int currentValue = 0;
public ProgressForm()
{
InitializeComponent();
}
public void ChangeValue(int newValue)
{
currentValue = newValue;
lblValue.Text = string.Format("Current value: {0}", currentValue);
lblValue.Refresh(); //Call Refresh to make the label update itself
}
}
static class Program
{
private static ProgressForm progressForm = null;
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
progressForm = new ProgressForm();
progressForm.Show();
doWork();
progressForm.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
int i = 0;
while (i < 100000)
{
progressForm.ChangeValue(i);
Thread.Sleep(1);
i++;
}
return;
}
}
You may use the following instead as you are trying to access UI control other than main thread (from which it is created).
while ( true )
{
Invoke ( ( Action ) (() =>
{
label.Text = val.ToString();
label.Refresh()
Application.DoEvents();
}));
Thread.Sleep( 100 );
}
I recommend you to use "backgroundworker".
First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.
private void btnDoIt_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Foo();
}
int total = 0;
private void Foo()
{
for (int i = 0; i <= 100000; i++)
{
total += i;
this.Text = i.ToString();
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run next process
}

How to Wake up a sleeping thread?

I made a thread at load event like below:
Thread checkAlert = null;
bool isStop = false;
private void frmMain_Load(object sender, EventArgs e)
{
checkAlert = new Thread(CheckAlert);
checkAlert.Start();
}
void CheckAlert()
{
while (!isStop)
{
Thread.Sleep(60000);
//do work here
}
}
Is there any way to resume the checkAlert thread during it's sleep period?( Thread.Sleep(60000);)
I tried using Thread.Interrupt() but it flows a ThreadInterruptedException, how should I handle this exception? or is there any way to resume the thread?
Edited:
I need to wake up the thread before the "sleep" end because when the user wants to quit the program, the program will have to wait for some time before it really quits ( checkAlert is still running) Is there any way to improve this case?
Based on your comments what it looks like is you need to re-design how CheckAlert works so it does not use Sleep's at all. What you should be doing is using a Timer instead.
System.Timers.Timer timer = null;
public FrmMain()
{
InitializeComponent();
timer = new System.Timers.Timer(60000);
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
//If you want OnTimedEvent to happen on the UI thread instead of a ThreadPool thread, uncomment the following line.
//timer.SynchronizingObject = this;
if(this.components == null)
this.components = new System.ComponentModel.Container();
//This makes it so when the form is disposed the timer will be disposed with it.
this.componets.Add(timer);
}
private void frmMain_Load(object sender, EventArgs e)
{
timer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
//It is good practice not to do complicated logic in a event handler
// if we move the logic to its own method it is much easier to test (you are writing unit tests, right? ;) )
CheckAlert();
}
void CheckAlert()
{
//do work here
}
private void frmMain_Close(object sender, EventArgs e)
{
timer.Stop();
}
If you want the thread to exit automatically when your program quits, simply make it a background thread.
checkAlert = new Thread(CheckAlert);
checkAlert.IsBackground = true;
checkAlert.Start();
It looks to me like you're trying to create a thread which handles two types of events: do something and stop running.
Rather than using a shared variable (isStop) and some other technique to interrupt the thread in order to do work, you might want to use threading events (not to be confused high-level UI Event objects) to control your thread.
AutoResetEvent stop = new AutoResetEvent(false);
AutoResetEvent check = new AutoResetEvent(false);
private void CheckAlert() {
WaitHandle[] handles = new WaitHandle[] { stop, check };
for (;;) {
switch (AutoResetEvent.WaitAny(handles)) {
case 0:
return;
case 1:
// do work
break;
}
}
}
Calling check.Set() in your code will trigger the "do work" branch in the thread and stop.Set() will cause the thread to terminate gracefully.
Once your code has called stop.Set() to terminate the thread, it can call the thread's Join() method to wait until the thread terminates.
EDIT
I misunderstood the question. I will leave the code above in case anyone finds it useful.
If all you want to do is have a thread that performs a task once a minute and stop on demand, you can use the following code:
AutoResetEvent stop = new AutoResetEvent(false);
void CheckAlert() {
var time = new TimeSpan(0, 1, 0); // one minute
while (!stop.WaitOne(time)) {
// do work
}
}
private Thread checkThread;
private void frmMain_Load(object sender, EventArgs e) {
checkThread = new Thread(CheckAlert);
checkThread.Start();
}
private void frmMain_Close(object sender, EventArgs e) {
stop.Set(); // signal thread to stop
checkThread.Join(); // wait for thread to terminate
}
You can see an explanation on how to wake a sleeping thread here:
https://msdn.microsoft.com/en-us/library/tttdef8x%28v=vs.100%29.aspx
and this is a complete example (as you can see, Thread.Interrupt is the good choise... however you have to catch it to continue normal thread execution):
public class HVCSensor : HVCDevice, IDisposable
{
private Thread myThread;
private const int execute_timeout = ((10 + 10 + 6 + 3 + 15 + 15 + 1 + 1 + 15 + 10) * 1000);
private bool disposed = false;
private bool paused = false;
public delegate void HVCResultsHandler(HVC_RESULT res);
public event HVCResultsHandler HVCResultsArrived;
private void OnHVCResultsArrived(HVC_RESULT res)
{
if (HVCResultsArrived != null) {
HVCResultsArrived(res);
}
}
public HVCSensor() {
myThread = new Thread(new ThreadStart(this.execute));
}
private void execute(){
while (!disposed) {
if (!paused && this.IsConnected)
{
HVC_RESULT outRes;
byte status;
try
{
this.ExecuteEx(execute_timeout, activeDetections, imageAcquire, out outRes, out status);
OnHVCResultsArrived(outRes);
}
catch (Exception ex) {
}
}
else {
try
{
Thread.Sleep(1000);
}
catch (ThreadInterruptedException e)
{
}
}
}
}
public HVC_EXECUTION_IMAGE imageAcquire
{
get;
set;
}
public HVC_EXECUTION_FLAG activeDetections
{
get;
set;
}
public void startDetection() {
if(myThread.ThreadState==ThreadState.Unstarted)
myThread.Start();
}
public void pauseDetection() {
paused = true;
}
public void resumeDetection() {
paused = false;
if (myThread.ThreadState == ThreadState.WaitSleepJoin)
myThread.Interrupt();
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
disposed = true;
myThread.Interrupt();
}
}

How to implement monitor and system.threading for the smokers situation

The problem that I am working on deals with out to use a functional Lock, or monitor structure, to provided exclusive access to only one member on separate threads. Below, is my class definition of the monitor (note that it is different than the actual monitor class given by c# library). What I am trying to do is make pictureboxes appear or disappear on my form.
I have attempted to add in an instance of the form so I can access the individual pictureboxes, however, my program seems to freeze.
namespace SmokersProblem
{
class monitor
{
Form1 form = new Form1();
Random num = new Random();
object obj = new object();
public monitor()
{
}
public void agent(){
form.pictureBox4.Visible = false;
int r = num.Next(1, 4);
if (r == 1)
{
// put lighter and paper on table
smoker1();
}
else if (r == 2)
{
// put lighter and tobacco on table
smoker2();
}
else
{
// put paper and tobacco on table
smoker3();
}
}
public void smoker1()
{
//lock (obj)
//{
form.pictureBox9.Visible = true;
form.pictureBox1.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
// }
}
public void smoker2()
{
//lock (obj)
//{
form.pictureBox10.Visible = true;
form.pictureBox3.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
//}
}
public void smoker3()
{
//lock (obj)
//{
form.pictureBox11.Visible = true;
form.pictureBox2.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
// }
}
}
}
Below is my form code, as you can see here, i try to create three seperate threads, one for each smoker.
namespace SmokersProblem
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Random rnd = new Random();
int num = rnd.Next(1, 4);
Object obj = new Object();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
pictureBox1.Visible = true;
pictureBox2.Visible = true;
pictureBox3.Visible = true;
pictureBox8.Visible = false;
pictureBox7.Visible = false;
pictureBox6.Visible = false;
monitor one = new monitor();
one.agent();
Thread vone = new Thread(one.smoker1);
Thread two = new Thread(one.smoker2);
Thread three = new Thread(one.smoker3);
vone.Start();
two.Start();
three.Start();
}
}
}
After implementing this, I went looking for the Smoker Thread Problem that it looks like OP is trying to implement. This code should be easily adaptable to that problem.
The reason your UI is freezing is that you're calling one.agent() without putting it in a new thread. one.agent() sleeps, which keeps your UI from processing events.
OK, I've implemented some code to do the smoker problem with labels. Obviously it could be improved, for instance by not coupling the form to the threads.
I put two different locking mechanisms in, and left one commented out.
Essentially, there are three labels that can either be "Smoking" or "Not Smoking". The main UI thread creates three threads:
Smoker1
Smoker2
Smoker3
Each of the threads continually tries to take the lock in a while loop. When they take the lock, they set their label to "Smoking", wait a few seconds, and then set their label to "Not Smoking". This uses thread safe code from this answer.
public partial class Form1 : Form
{
private bool running = false;
public Label OneLabel { get; set; }
public Label TwoLabel { get; set; }
public Label ThreeLabel { get; set; }
private MyMonitor one;
private Thread vone;
private Thread two;
private Thread three;
public Form1()
{
InitializeComponent();
OneLabel = new Label();
OneLabel.Text = "Not Smoking";
OneLabel.Location = new Point(10, 50);
OneLabel.AutoSize = true;
this.Controls.Add(OneLabel);
TwoLabel = new Label();
TwoLabel.Text = "Not Smoking";
TwoLabel.Location = new Point(150, 50);
this.Controls.Add(TwoLabel);
ThreeLabel = new Label();
ThreeLabel.Text = "Not Smoking";
ThreeLabel.Location = new Point(300, 50);
this.Controls.Add(ThreeLabel);
}
private void MainButton_Click(object sender, EventArgs e)
{
if (!running)
{
vone.Start();
two.Start();
three.Start();
MainButton.Text = "Stop";
running = true;
}
else
{
one.RequestStop();
MainButton.Text = "Run";
running = false;
}
}
private void Form1_Load(object sender, EventArgs e)
{
one = new MyMonitor(this);
vone = new Thread(one.Smoker1);
two = new Thread(one.Smoker2);
three = new Thread(one.Smoker3);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (running)
{
one.RequestStop();
running = false;
}
}
}
class MyMonitor
{
private int x = 1;
private Object obj = new Object();
private Form1 _form;
bool _finished = false;
public MyMonitor(Form1 form)
{
_form = form;
}
public void Smoker1()
{
while (!_finished)
{
//lock (obj)
//{
// _form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void Smoker2()
{
while (!_finished)
{
//lock (obj)
//{
// _form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void Smoker3()
{
while (!_finished)
{
//lock (obj)
//{
// _form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void RequestStop()
{
_finished = true;
}
}
//Thread Safe Extension Method
public static class Extensions
{
private delegate void SetPropertyThreadSafeDelegate<TResult>(Control #this, Expression<Func<TResult>> property, TResult value);
public static void SetPropertyThreadSafe<TResult>(this Control #this, Expression<Func<TResult>> property, TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
if (propertyInfo == null ||
!#this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
#this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (#this.InvokeRequired)
{
#this.Invoke(new SetPropertyThreadSafeDelegate<TResult>(SetPropertyThreadSafe), new object[] { #this, property, value });
}
else
{
#this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, #this, new object[] { value });
}
}
}
my program seems to freeze
My one.agent() is the part of the code that allows one of the smokers
to be called on, so they can smoke. Why wouldnt I want it in the main
code?
Because you shouldn't be using Sleep() from the main UI thread, which is what happens when you call one.agent() from the Button Click event. When Sleep(5000) is hit, you're telling the forms main UI thread to not do ANYTHING for five seconds, thus the freezing you're seeing.
To fix this, you'd need agent() to execute smoker1(), smoker2(), or smoker3() in a separate thread like you're doing down below.
There are several other problems with the code, however, that must also be addressed before you can "fix" your code...
The next problem lies in you creating a new instance of Form1() inside your monitor() class. This instance of Form1() is not the same one that is visible on your screen. Acting upon it is modifying an invisible form that has never even been shown. To act upon the form that is actually visible on your screen would require you to either (a) pass a reference to it into your monitor() class when you create it, or (b) have your monitor() class raise custom events that Form1() subscribes to, again when it creates monitor().
The last problem lies in you attempting to change UI controls from within a thread other than the main UI thread. This will result in a cross-thread exception (unless you've turn this off, which you shouldn't). There are various ways to overcome this problem, the most basic of which involves using delegates and the Invoke() method of the Form/Control to which you are trying to update.

Refresh Progressbar UI using Thread

I have a FTP proccess that run without UI. and have a winform that use this ftp control. in that window I have a progressbar that show the ftp upload progress. The progress arrives to the window via interfase that is updated on the underliying presenter (I'm using MVP pattern).
My problem is when try to update the progress, it allways throw me this exception.
Through threads illegal operation: control 'prgProgresoSubido' is accessed from a thread other than that in which you created it.
That problem persists even if I use a BackGroundWorker in the Form.
// This is a delegated on presenter when a File finish to upload
void client_FileUploadCompletedHandler(object sender, FileUploadCompletedEventArgs e)
{
string log = string.Format("{0} Upload from {1} to {2} is completed. Length: {3}. ",
DateTime.Now, e.LocalFile.FullName, e.ServerPath, e.LocalFile.Length);
archivosSubidos += 1;
_Publicacion.ProgresoSubida = (int)((archivosSubidos / archivosXSubir) * 100);
//this.lstLog.Items.Add(log);
//this.lstLog.SelectedIndex = this.lstLog.Items.Count - 1;
}
// This is My interfase
public interface IPublicacion
{
...
int ProgresoSubida { set; }
}
/// And Here is the implementartion of the interfase on the form
public partial class PublicarForm : Form ,IPublicacion
{
//Credenciales para conectarse al servicio FTP
public FTPClientManager client = null;
public XmlDocument conf = new XmlDocument();
public string workingDir = null;
public webTalk wt = new webTalk();
private readonly PublicacionesWebBL _Publicador;
public PublicarForm()
{
InitializeComponent();
String[] laPath = { System.AppDomain.CurrentDomain.BaseDirectory};
String lcPath = System.IO.Path.Combine(laPath);
_Publicador = new PublicacionesWebBL(this, lcPath);
}
public int ProgresoSubida
{
set
{
// This is my prograss bar, here it throw the exception.
prgProgresoSubido.Value = value;
}
}
}
How can I do to avoid this problem ?
In general, all updates to the User Interface and Controls has to be done from the main thread (event dispatcher). If you attempt to modify the properties of a control from a different thread you will get an exception.
You must call Control.Invoke to invoke on the event dispatcher the method that updates your UI
Control.Invoke
Here, place a button and a label on a form, then try this
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(TestThread));
t.Start();
}
private void TestThread()
{
for (int i = 0; i < 10; i++)
{
UpdateCounter(i);
Thread.Sleep(1000);
}
}
private void UpdateCounter(int i)
{
if (label1.InvokeRequired)
{
label1.Invoke(new ThreadStart(delegate { UpdateCounter(i); }));
}
else
{
label1.Text = i.ToString();
}
}
}
Realize, that if you are firing an event from a thread, that the event will be on the same Thread. Therefore, if that thread is not the event dispatcher, you'll need to invoke.
Also, there may be mechanisms that BackgroundWorker gives you (As the commentator said) that simplify this for you, but I've never used it before so I'll leave that up to you to investigate.
As Alan has just pointed out, you must do all operations with UI controls in UI thread.
Just modify your property like this:
public int ProgresoSubida
{
set
{
MethodInvoker invoker = delegate
{
prgProgresoSubido.Value = value;
}
if (this.InvokeRequired)
{
Invoke(invoker);
}
else
{
invoker();
}
}
}

Thread safe form manipulation between two forms (WinForms C#)

I have two forms, the main form and one that pops up as a modal dialog. From a process spawned in the main form, I want to dynamically update the text on the modal dialog. Here's what I have:
In the main form, I do this:
// show the wait modal
var modal = new WaitDialog { Owner = this };
// thread the packaging
var thread = new Thread(() => Packager.PackageUpdates(clients, version, modal));
thread.Start();
// hopefully it worked ...
if (modal.ShowDialog() != DialogResult.OK)
{
throw new Exception("Something failed, miserably.");
}
The PackageUpdates method takes the modal dialog, and does this:
// quick update and sleep for a sec ...
modal.SetWaitLabelText("Downloading update package...");
Thread.Sleep(2000);
modal.SetWaitLabelText("Re-packaging update...");
To be thread safe, I do this in the modal dialog:
public void SetWaitLabelText(string text)
{
if (lblWaitMessage.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
lblWaitMessage.Text = text;
}
}
Everything works great ... most of the time. Every three or four times that the modal pops up, I get an exception on the lblWaitMessage.Text = text; and it's not invoking the command.
Am I missing something in this setup?
Like #Hans Passant pointed out, you should wait for the modal.Load-event. One good option is to use the ManualResetEvent to inform your thread to wait until that happens.
The WaitOne method will block the thread until the Set method is called. Here's a very simple setup which should do the trick.
public partial class Form1 : Form
{
ManualResetEvent m_ResetEvent;
public Form1()
{
InitializeComponent();
m_ResetEvent = new ManualResetEvent(false);
}
private void button1_Click(object sender, EventArgs e)
{
Dialog d = new Dialog { Owner = this, ResetEvent = m_ResetEvent };
var thread = new Thread(new ParameterizedThreadStart(DoSomething));
thread.Start(d);
if (d.ShowDialog() != System.Windows.Forms.DialogResult.OK)
{
throw new Exception("Something terrible happened");
}
}
private void DoSomething(object modal)
{
Dialog d = (Dialog)modal;
// Block the thread!
m_ResetEvent.WaitOne();
for (int i = 0; i < 1000; i++)
{
d.SetWaitLabelText(i.ToString());
Thread.Sleep(1000);
}
}
}
And here is the modal form
public partial class Dialog : Form
{
public Form Owner { get; set; }
public ManualResetEvent ResetEvent { get; set; }
public Dialog()
{
InitializeComponent();
}
public void SetWaitLabelText(string text)
{
if (label1.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
label1.Text = text;
}
}
private void Dialog_Load(object sender, EventArgs e)
{
// Set the event, thus unblocking the other thread
ResetEvent.Set();
}
}
I think you should rewrite the code to let thread.Start() isn't called before modal.ShowDialog().
As a workaround, you can try this:
public void SetWaitLabelText(string text) {
Invoke(new Action<string>(SetWaitLabelText2), text);
}
void SetWaitLabelText2(string text) {
lblWaitMessage.Text = text;
}
The first method always uses Invoke, regardless the value of InvokeRequired. The second method actually does the thing. This pattern is usable when you always call the function from another thread.

Categories