Here's the thing, I have started with C# and I want to do something like this:
I have a Windows Form Application with one button and the picturebox.
Clicking on a button should result in turning property "Running" on true/false, by the actual state. That is done.
Also, it should result into turning on the script that will be doing constantly a job as the program runs. This "job" will be described in Run() method. And I want this method to be executed only when Running == true, in moment it becomes false, the method should end.
So I decided put it into the thread and in the method where I switch between Running = true and Running = false, I try to start the thread and abort it.
Why do I want to do this? Because I want to be able to turn program working on and off by the button I mentioned at the beginning.
This is what I came up with:
Thread thProgram;
public Form1()
{
InitializeComponent();
thProgram = new Thread(new ThreadStart(this.Run));
}
private bool Running = false;
public void Run()
{
int i = 0;
while(this.Running)
{
i++;
}
MessageBox.Show("Terminated");
}
// handling bot activation button (changing color of a pictureBox1), switching this.Running property
private void button1_Click(object sender, EventArgs e)
{
if(this.Running)
{
thProgram.Abort();
pictureBox1.BackColor = Color.Red;
this.Running = false;
}
else
{
thProgram.Start();
pictureBox1.BackColor = Color.Lime;
this.Running = true;
}
}
I can click the button exactly twice, and it appears that everything is all right... but when I click it for the third time, error pops up:
(it highlights the line "thProgram.Start();"
An unhandled exception of type 'System.Threading.ThreadStateException' occurred in mscorlib.dll
Additional information: Thread is running or terminated; it cannot restart.
Thanks in advance for any help you are able to provide me with.
The exception is Self-explaining
When you press the button for the first time, the thread starts and falls into its main loop.
The second button press aborts the thread (which is always a bad idea. That flag you used is enough) and the thread terminates.
The third button press? From MSDN documentation for Thread.Start() :
Once the thread terminates, it cannot be restarted with another call to Start.
To freeze a thread without terminating it, I suggest using AutoResetEvent:
public Form1()
{
InitializeComponent();
thProgram = new Thread(new ThreadStart(this.Run));
}
private bool Running = false;
private AutoResetEvent ThreadHandle = new AutoResetEvent(false);
public void Run()
{
int i = 0;
while(true)
{
ThreadHandle.WaitOne();
i++;
}
MessageBox.Show("Terminated");
}
// handling bot activation button (changing color of a pictureBox1), switching this.Running property
private void button1_Click(object sender, EventArgs e)
{
if(this.Running)
{
thProgram.Abort();
pictureBox1.BackColor = Color.Red;
this.ThreadHandle.Reset();
this.Running = false;
}
else
{
thProgram.Start();
pictureBox1.BackColor = Color.Lime;
this.ThreadHandle.Set();
this.Running = true;
}
}
Related
I'm getting some troubles with my Winforms C# app.
I wish to make form named Popup closing after some operations in main thread are done. The problem is an exception caused by cross-thread form closing.
private void loginButton_Click(object sender, EventArgs e)
{
LoginProcess.Start(); // Running Form.show() in new thread
ActiveAcc.IsValid = false;
ActiveAcc.Username = userBox.Text;
try
{
LoginCheck(userBox.Text, passBox.Text);
}
catch (IOException)
{
MessageBox.Show("..");
return;
}
catch (SocketException)
{
MessageBox.Show("..");
return;
}
if (ActiveAcc.IsValid)
{
MessageBox.Show("..");
Close();
}
else
{
Popup.Close(); // Error caused by closing form from different thread
MessageBox.Show("");
}
}
public Login() // 'Main' form constructor
{
InitializeComponent();
ActiveAcc = new Account();
Popup = new LoginWaiter();
LoginProcess = new Thread(Popup.Show); //Popup is an ordinary Form
}
I've been trying to use various tools such as LoginProcess.Abort() or Popup.Dispose() to make it work properly, but even if app is working on runtime environment its still unstable due to Exceptions which are thrown.
I would be grateful for any help, and I am sorry for ambiguities in issue describing.
Why don't you let the UI thread do UI stuff like opening and closing Forms, and spawn the other thread (or background worker, or async task) to do the other stuff?
IMO, having other threads attempt to interact with elements on the UI thread (e.g., have a background thread directly set the text of a label or some such) is asking for heartache.
If you simply must keep your code as is, here is a fairly simple thing you could do. In Popup, add a static bool that defaults to true. Also in Popup, add a timer task that once every X milliseconds checks the status of that boolean. If it finds that the value has been set to false, let Popup tell itself to close within that timer tick.
I'm not crazy about this design, but it could look something like:
public partial class Popup : Form
{
public static bool StayVisible { get; set; }
private System.Windows.Forms.Timer timer1;
public Popup()
{
StayVisible = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!StayVisible) this.Close();
}
}
Then, from another thread, when you want Popup to close, call
Popup.StayVisible = false;
Better yet, you would fire an event that Popup would receive so that it could close itself. Since you intend to use multiple threads, you'll have to deal with raising events cross-thread.
Ok i'm begginer at coding
So what i'm trying to do is button who gonna wait for
the user to click on one of the multiple other button to continue
void Mainbutton()
{
//the program run throw so method
//Wait for the user to choose one button (I made a numeric pad with buttons)
//Then use this information to work
}
I know my english isnt that good
Thanks a lot
Try something like this:
bool gotResponse = false;
//you need to run MainTask in a different thread
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
void DoWork()
{
//do some work
//when something else needed from user then popup message
MessageBox.Show("say whatever you need to say");
while(!gotResponse)
{
//note: this loop doesn't stop until gotResponse = true;
}
//do rest of your work
}
private button_Click(object sender, RoutedEventArgs e)
{
gotResponse = true;
}
Background
From the valuable advice I received here I have now moved all of my database intensive code to a backgroundworker, specifically the direct calls to the database. That code is executed during the backgroundworker's DoWork event. If a DataTable is returned during the DoWork event, I set that DataTable to a class-wide variable. This is done, to avoid having to invoke the controls requiring the DataTable every time I run this code.
While that code is being executed, I have a label that is updated in the main UI thread, to let the user know that something is occurring. To update the label I use a timer, such that every 750 ms a "." is appended to the label's string.
The first thing that I noticed was that the backgroundworker's RunWorkerCompleted event wasn't triggering. To solve this I did an Application.DoEvents(); before each call I made to the backgroundworker. It was ugly, but it caused the event to trigger. If anyone has an alternative to fix this, I am all ears.
I then came across an interesting predicament. If I run the program within Visual Studio 2010, in the debugging mode, I get an InvalidOperationException error stating that the "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." This error occurs during the backgroundworker's RunWorkerCompleted event, where I set the text of a label in the main UI thread. But, when I launch the application directly, through the executable, it works exactly as desired (i.e. the label's text is set correctly).
Question
Can anyone explain what is going on / offer advice on how to improve upon this?
Code
I can't post all of the code involved, but here's some relevant stuff:
namespace Test
{
public partial class frmMain : Form
{
public static Boolean bStatus = false;
static Boolean bTimer = false;
System.Timers.Timer MyTimer = new System.Timers.Timer();
public frmMain()
{
InitializeComponent();
MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
MyTimer.Interval = 750; // Every 3/4 of a second
ExampleTrigger();
}
/// <Insert>Lots of unshown code here</Insert>
private void ExampleTrigger()
{
// This is used to simulate an event that would require the backgroundworker
Application.DoEvents();
bgw.RunWorkerAsync(0);
WaitText("Example - 1");
}
private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
bTimer = true;
}
// Update status text
private void WaitText(string txt)
{
MyTimer.Enabled = true;
lblStatus.Text = txt;
bStatus = false;
while (!bStatus)
{
if (bTimer)
{
txt = txt + ".";
lblStatus.Text = txt;
lblStatus.Update();
bTimer = false;
}
}
MyTimer.Enabled = false;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int iSelect = (int)e.Argument;
switch (iSelect)
{
case 0:
// Hit the database
break;
/// <Insert>Other cases here</Insert>
default:
// Do something magical!
break;
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bStatus = true;
lblStatus.Text = "Ready!"; // This is where the exception occurs!
}
}
}
Never run a while() loop like that in the UI thread.
You're freezing the UI until the loop terminates; this defeats the purpose.
In addition, System.Timers.Timer doesn't run callbacks in the UI thread.
Use a WinForms Timer instead.
Once you switch to a WinForms timer, you can simply append to the label inside the timer callback, and disable the timer when the operation finishes.
When a user clicks on Run, the application runs through a lot of code to generate a model and display it in a Chart. The Run takes about 1-2 minutes to run. I also have a Cancel button that gets enabled after the Run button is clicked. I am working with DotSpatial, so my buttons are on a plugin panel in a ribbon UI. The click event on the Run and Cancel start in the plugin, which calls the back-end class's code Run and Click.
When the user hits cancel after the run starts, there is a delay, but the cancel method is invokes and executes, but the run never stops and we eventually see the chart display. So, I'm thinking I need a separate thread for the Run. I'm fairly new to programming, and never worked with Threading. I've looked into it and added the below code, but my thread method isn't running. Here's my code:
The Run button is clicked:
This is at the top:
//check to see if RunModel thread needs to stop or continue
private volatile bool stopRun = false;
private Thread runThread;
Then this is the method that's called from the click event:
public void btnRun_testingThread(object sender, EventArgs e)
{
//create a new thread to run the RunModel
if (runThread == null)
{
//we don't want to stop this thread
stopRun = false;
runThread = new Thread(RunModel);
runThread.Start(); <--this isn't doing anything
}
So, I would think that when the code gets to the runThread.Start(), it would jump into my RunModel method and start running through the code. But it doesn't. Additionally, I'll want to cancel out of this thread (once I have it working correctly), so I have this, which gets called from the cancel click method:
private void StopRunThread()
{
if (runThread != null)
{
//we want to stop the thread
stopRun = true;
//gracefully pause until the thread exits
runThread.Join();
runThread = null;
}
}
Then the this is the RunModel() where I'm checking occasionally to see if the stopRun bool has changed.
public void RunModel()
{
...some code.....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
....some more code....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
}
And the cancel button click method:
public void btnCancel_Click(Object sender, EventArgs e)
{
stopRun = true;
StopRunThread();
//the model run has been canceled
....some code.....
}
Any help on getting the thread.start to actually run the Run method? Then do I need to constantly check the volatile bool in the run in order to clean everything up if it's being stopped? Thanks!
I think you'd be best looking at the BackgroundWorker - this essentially runs separately but can watch out for cancellation commands. Make sure you add 'WorkerSupportCancellation' when you initialise it:
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); // This does the job ...
backgroundWorker1.WorkerSupportsCancellation = true; // This allows cancellation.
Then on click you can start your process:
public void btnRun_testingThread(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
Your cancel button can issue a cancellation request:
public void btnCancel_Click(Object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
Then your worker can monitor for this as it's doing it's work ...
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (backgroundWorker1.CancellationPending)
{
break;
}
else
{
// Do whatever you're doing.
}
}
e.Result = backgroundWorker1.CancellationPending ? null : orders;
}
You can enhance this further by adding progress bars etc., but that gets a bit more complicated so I won't go into it here.
Considering new info provided in commend I believe you just missed a start of the RunModel() method in debugger because of wrong assumption regarding thread.Start() method behaviour.
Please see a note from MSDN, Thread.Start Method
Once a thread is in the ThreadState.Running state, the operating
system can schedule it for execution. The thread begins executing
at the first line of the method represented by the ThreadStart or
ParameterizedThreadStart delegate supplied to the thread constructor.
Small demonstration that thread start takes some time bits, for me it starts in 38-40 milliseconds:
Stopwatch watch = new Stopwatch();
Thread thread = new Thread((ThreadStart)watch.Stop);
thread.Start();
watch.Start();
Thread.Sleep(5000);
double startedAfter = watch.ElapsedMilliseconds;
Since .NET Framework 4.0 consider using TPL Tasks rather than threads explicitly, some pros:
You can easily synchronize with UI thread by passing in a Task UI Thread synchronization context
You can easily stop a Taks using CancellationToken
private void launchbutton_Click(object sender, EventArgs e)
{
launchbutton.Enabled = false;
Process proc = new Process();
proc.EnableRaisingEvents = true;
proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//The arguments/filename is set here, just removed for privacy.
proc.Exited += new EventHandler(procExit);
proc.Start();
}
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY","WOOT");
Thread.Sleep(2000);
launchbutton.Enabled = true;
}
2 Seconds after I quit the created process, my program crashes. Why?
You're modifying a winform control on a different thread than the one that created that control (the main UI thread). Winform controls are not thread-safe and typically will throw an exception if you modify their state from any thread other than the one that created it.
You can accomplish this using the InvokeRequired property and BeginInvoke method found on the Form or control object.
For example, something like this:
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY", "WOOT");
Thread.Sleep(2000);
// ProcessStatus is just a class I made up to demonstrate passing data back to the UI
processComplete(new ProcessStatus { Success = true });
}
private void processComplete(ProcessStatus status)
{
if (this.InvokeRequired)
{
// We are in the wrong thread! We need to use BeginInvoke in order to execute on the correct thread.
// create a delegate pointing back to this same function, passing in the same data
this.BeginInvoke(new Action<ProcessStatus>(this.processComplete), status);
}
else
{
// check status info
if (status.Success)
{
// handle success, if applicable
}
else
{
// handle failure, if applicable
}
// this line of code is now safe to execute, because the BeginInvoke method ensured that the correct thread was used to execute this code.
launchbutton.Enabled = true;
}
}