How do I avoid the dreaded Application.DoEvents() when multithreading [closed] - c#

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
So I've been reading a lot of articles that have been discouraging the use of Application.DoEvents() and even saying that it should never be used but I can't seem to find a good alternative for my scenario...
The application that I am working on has a method that is called by a this.Shown event when the main GUI form first launches. The method does some work that takes about a minute of time so the same method also creates a form that is essentially a custom made progress bar. Keep in mind that this process is currently single threaded so when this method is doing work the main GUI and and progress bar become non-responsive. If the user clicks anywhere during this time, the screens go blank. So I'm working on putting some of the work that this method does in a BackgroundWorker thread. Here is what I've come up with:
private BackgroundWorker Bgw = new BackgroundWorker();
private int LoadPercentage = 0;
//this sub is executed on the main UI thread
public void RunBgw()
{
bool StopThread = false;
//this object should be created in this method and needs to be updated as the child thread is doing work
MyCustomDialog dialog = new MyCustomDialog();
dialog.UpdateProgress(0, "My message");
dialog.Show();
this.Invalidate();
this.Refresh();
//critical properties to set if you want to report progress/be able to cancel the operation
Bgw.WorkerSupportsCancellation = true;
Bgw.WorkerReportsProgress = true;
//add handlers to Bgw so events will fire
Bgw.DoWork += new DoWorkEventHandler(Bgw_DoWork);
Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);
//fire off thread
Bgw.RunWorkerAsync();
while (Bgw.IsBusy == true)
{
if (BW.CancellationPending == true)
{
StopThread = true;
break;
}
Application.DoEvents();
if(LoadPercentage == 10)
{
dialog.UpdateProgress(LoadPercentage, "Still working...");
this.Invalidate();
this.Refresh();
}
if(LoadPercentage == 50)
{
dialog.UpdateProgress(LoadPercentage, "Halfway done...");
this.Invalidate();
this.Refresh();
}
// etc...
//slow down loop so it doesnt take up all the CPU
Thread.Sleep(200);
}
if(!StopThread) {
//continue with something else.
}
}
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker BgwLocal = sender as BackgroundWorker;
if ((BgwLocal.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
TimeConsumingWork();
BgwLocal.ReportProgress(10); //report progress back to the main UI thread
TimeConsumingWork();
BgwLocal.ReportProgress(15, SomeGuiIcon); //pass back specific gui icon
TimeConsumingWork();
BgwLocal.ReportProgress(50);
// etc...
}
}
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
LoadPercentage = e.ProgressPercentage; //set current percentage of progress
if(LoadPercentage == 15)
{
GuiIcon1 = (Icon)e.UserState; //set gui icon
}
}
private void Bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
//error handling
}
else if (!(e.Error == null))
{
//error handling
}
else
{
//success
}
}
Everything is working well except that error handling has proven to be difficult and messy. Is there a better way of threading work while updating an existing object in the main thread??
Thanks for reading.

You should not be blocking the UI thread with this code:
while (Bgw.IsBusy == true) { ... }
Instead, allow RunBgw() to return to the caller. Use the events present on the BackgroundWorker to know when it has completed. Specifically
Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
reports on progress by calling
Bgw_ProgressChanged
and
Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);
causes
Bgw_RunWorkerCompleted
to be invoked when the BackgroundWorker is done.
Update the progress bar from within Bgw_ProgressChanged.
Windows UI's are event driven. Your code is not making use of events to control program execution.

Related

Check multiple checkbox.checked state inside a do while cycle

I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.

How do I make a repeating button with a thread in c#?

I've looked at some guides and none of them have gotten me all the way there. I've never made a thread, discussed a thread, or seen a thread at the grocery store, so this may be a problem. Currently. I'm trying:
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
{
ThreadStart HUp = new ThreadStart(dothis);
t = new Thread(HUp);
t.Start();
}
}
public void dothis()
{
if (intHour < 23)
intHour = intHour += intStep;
lblTimerHour.Text = intHour.ToString("00");
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
t.Abort();
}
}
That gets me InvalidOperationException was unhandled on the
lblTimerHour.Text = intHour.ToString("00");
line. I read what that means and... it might as well be in Mandarin, I kind of get the general concept-ish of what's going wrong, but it's painfully fuzzy. If you asked me the first step in fixing it I'd look at you like a deer in the headlights. We just haven't gotten that far in my class yet.
The problem here is that the label you are trying to update is owned by the main thread (i.e. what the UI runs on), and that means that only that thread can access/update it. So, since you are in a different thread, you need to tell the UI thread to update the label for you.
Something like this would work:
Action updateLabel = () => lblTimerHour.Text = intHour.ToString("00");
lblTimerHour.BeginInvoke(updateLabel);
What this does is tell the lblTimerHour to invoke the action you define above (updateLabel).
See this post: How to update the GUI from another thread in C#?
lblTimerHour.Invoke((MethodInvoker)delegate {
//Do what you need to do with the label
lblTimerHour.Text = intHour.ToString("00");
});
Edit
This should do the trick:
public void dothis()
{
do
{
if (intHour < 23)
intHour = intHour += intStep;
lblTimerHour.Invoke((MethodInvoker)delegate {
//Update the label from the GUI thread
lblTimerHour.Text = intHour.ToString("00");
});
//Pause 1 sec. Won't freeze the gui since it's in another thread
System.Thread.Sleep(1000);
}while(true); //Thread is killed on mouse up
}
Well, let's take a look and see what you already have.
First, I see you did this.
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
ThreadStart HUp = new ThreadStart(dothis);
t = new Thread(HUp);
t.Start();
}
While this certainly is not the freshest stuff around it will still work. If you wanted some fresher ingredients then you might go with this instead.
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
Task.Factory.StartNew(dothis);
}
Second, I see this.
public void dothis()
{
if (intHour < 23) intHour = intHour += intStep;
lblTimerHour.Text = intHour.ToString("00");
}
The problem here is that you are attempting to update a UI control from a thread other than the main UI thread. You see UI controls have what is called thread affinity. They can only ever be accessed from the thread that created them. What you have will lead to all kinds of unpredictable problems up to and including tearing a whole in spacetime.
A better option would be to do this.
public void dothis()
{
while (intHour < 23)
{
intHour = intHour += intStep;
lblTimerHour.Invoke((Action)(
() =>
{
lblTimerHour.Text = intHour.ToString("00");
}));
}
}
I assumed that you were missing the loop so I added it. While I cannot say that I personally have a taste for this kind of thing it is much easier to swallow. The real problem here is that the worker thread really does not do a whole lot of useful work. And then to top it off we have to use an awkward marshaling operation to transfer the result back to the UI thread. It is not pretty, but it will work.
And finally that brings me to this.
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
t.Abort();
}
You are attempting to abort a thread which is highly inadvisable. The problem is that it yanks control from the thread at unpredictable times. That thread might be in the middle of a write to data structure which would corrupt it. This is actually a pretty bad problem because any data structure in the process of being manipulated from any one of the frames on the call stack could be in an inconsistent state. This includes code you did not write. That is why it is hard to say what you may or may not be corrupting by doing this.
What you need to consider instead is using the cooperative cancellation mechanisms. This includes the use of CancellationTokenSource and CancellationToken. Here is how it might look once we put everything together.
private CancellationTokenSource cts = null;
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
cts = new CancellationTokenSource();
Task.Factory.StartNew(() => dothis(cts.Token));
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
cts.Cancel();
}
public void dothis(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
intHour += intStep;
lblTimerHour.Invoke((Action)(
() =>
{
lblTimerHour.Text = intHour.ToString("00");
}));
Thread.Sleep(1000);
}
}
What this does is signal that the worker thread should gracefully shutdown on its own. This gives the worker thread a chance to tidy things up before eventually terminating itself.
If you want to update the UI every X period of time then there are already existing tools for this; a Timer will do exactly what you want, and it will be much more efficient and easier to code than creating a new thread that just spends most of its time napping. Additionally, aborting threads is a very bad sign to see. Avoid it at all costs.
First create the timer and configure it in the constructor:
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private int hour = 0;
private int step = 0;
public Form1()
{
InitializeComponent();
timer.Tick += timer_Tick;
timer.Interval = 1000;
}
Have the Tick event do whatever should be done whenever it ticks.
private void timer_Tick(object sender, EventArgs e)
{
if (hour < 23)
{
hour += step;
lblTimerHour.Text = hour.ToString("00");
}
}
Then just start the timer when you want it to start ticking and stop the timer when you want it to stop:
private void btnHUp_MouseDown(object sender, MouseEventArgs e)
{
timer.Start();
}
private void btnHUp_MouseUp(object sender, MouseEventArgs e)
{
timer.Stop();
}
The timer will automatically ensure that the Tick event handler runs in the UI thread, and it won't block the UI thread (or any other thread) when its waiting for the next event to happen, it will just do nothing.

C# - How to work with background worker with custom code -Run,Pause,Stop?

I am working with Background Worker but neither i am able to synchronize my progress bar nor able to stop or abort the process.
in my dowork function
void bw_DoWork(object sender, DoWorkEventArgs e)
{
if(bw.CancellationPending==true)
{
e.cancel=true;
return;
}
else
{
e.Result = abc();
}
}
int abc()
{
//my work
Count++;
return count;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(bw.CancellationPending==true)
{
button17.Visibility = Visibility.Visible;
label1.Content = "Aborted";
}
button17.Visibility = Visibility.Visible;
label1.Content = "Completed";
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if(bw.IsBusy)
{
bw.CancelAsync();
}
}
Now i want to know how could i Synchronize my Progress Bar and how to exit from the process?
Have you set the BackgroundWorker.WorkerReportsProgress && BackgroundWorker.WorkerSupportsCancellation properties on your instance to be true?
e.g.
var myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.WorkerReportsProgress = true;
myBackgroundWorker.WorkerSupportsCancellation = true;
//the rest of the init
If you want to report progress, you need to call the BackgroundWorker.ReportProgress() method from inside your DoWork.
This is a rubbish and trivial answer but give the Task Parallel library a whirl.
http://msdn.microsoft.com/en-us/library/dd537608.aspx
This library encapsulates threads as discrete Task objects. It supports cancellation.
Be warned that in a worker thread, pause and cancellation operation have to be supported by the worker code itself, by polling pause/cancel flags and tokens. You cannot safely achieve these operations with threads alone.
It is a nicer pattern to work with
As for your question, 2 flags are required to support your operations. You will be need to check them at intervals during the worker code.
bool pause = false;
bool cancel = false;
void DoWork()
{
try
{
...
//periodically check the flags
if(cancel) return;
while(paused){}; //spin on pause
...
}
finally
{
//cleanup operation
}
}
Alastair Pitts' answer illustrates how background worker supports these features.
So does MSDN ;) http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx
(You might want to check out this other SO question/answer for details about the new async facility! It greatly improves the quality of life of developing this kind of operations!)
BackgroundWorker is event-based, basic usage is the following (the link provides many useful additional details):
var worker = new BackgroundWorker();
// The following two props must be true:
// #1: The worker will be enabled to signal its progress
worker.WorkerReportsProgress = true;
// #2: The worker will accept cancellation
worker.WorkerSupportsCancellation = true;
// Now the events:
worker.DoWork += (s,e) =>
{
int i = 0; // This goes from 0 to 100
// Do code, update 'i'
worker.ReportProgress(i);
worker.CancelAsync(); //... to cancel the worker if needed
// WARNING: This code *cannot* interact with the UI because
// it's running in a different thread
};
worker.ProgressChanged += (s,e)=>
{
// This is executed when you call ReportProgress() from DoWork() handler
// IMPORTANT: All UI interaction **must** happen here
// e.ProgressPercentage gives you the value of the parameter you passed to
// ReportProgress() (this mechanism is a perfect fit for a progress bar!)
};
worker.RunWorkerCompleted+= (s,e) =>
{
// code here runs when DoWork() is done, is canceled or throws.
// To check what happened, the link provides this sample code:
if (e.Cancelled == true)
{
// Cancelled!
}
else if (e.Error != null)
{
// Exception !
}
else
{
// Work completed!
}
};
worker.RunWorkerAsync();
It's important to know that (extracted from the link above):
You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.
UPDATE Lambdas here are used to keep code compact. You can obviously use "normal" handlers or whatever other method of attaching code to events you like/want/need.

Form Not Responding when any other operation performed in C#

I have a form (Developed in C# using VS2010) with a Progress Bar.
It's kind of stopwatch form where I fill the progress bar in say 10secs.... As Time elapses, Progress bar fills accordingly.... Means after 5secs, Progress Bar will be filled 50% and so on....
I used a for loop to perform this operation:-
for(int count=0;count<200;count++)
{
progbar.performstep();
progbar.update();
update();
Thread.Sleep(50);
}
I have used Thread.Sleep of 50msec so that progress bar updates smoothly.
For 1sec, it increments in steps.
Problem is if I do anyother operation like Moving this form or even clicking on another icon on desktops makes this form "Not Responding".... But it perfoms the operation and at the end of 10 secs, It regains it's state and shows the pop up indicating 10secs are elapsed with Progress Bar Full.
Thanks for help and Sorry for using such complicated language.
Regards,
Swanand
Update: I solved this problem with the help of Answers below.... One common mistake I got to know is forgetting "Applications.DoEvents()" along with "update()".... If you enter this line, there are less chances of getting "hanged"!
You're performing a long-running operation on the UI thread, which means that the UI "message loop" (responsible for handling events such as user input and updating the screen) doesn't get a chance to run.
You should perform the long-running operation on a different thread (whether one you create yourself or a background thread) and either use BackgroundWorker to easily update your progress bar, or use Control.Invoke/BeginInvoke to marshall a delegate call back to the UI thread when you need to update the UI. (You mustn't update controls from the wrong thread.)
If your only UI interaction is filling in a progress bar, I suggest using BackgroundWorker.
If you're not really doing "real" work, just waiting for time to pass, you could use a System.Windows.Forms.Timer instead of all of this, however. That will "tick" on the UI thread, but won't block the UI thread between ticks. You should only use this if you don't have a lot of work to do though - if it really is just updating a progress bar, not (say) processing a file etc. Note that you shouldn't rely on the timer firing exactly "on time" - you should probably set the position of the progress bar based on the observed time, rather than the observed number of ticks.
You are blocking the UI thread, which means it isn't processing events such as "paint". To do this properly, you should be using something like BackgroundWorker, and just updating the UI from the progress event.
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyForm());
}
}
class MyForm : Form
{
Button btn;
BackgroundWorker worker;
ProgressBar bar;
public MyForm()
{
Controls.Add(btn = new Button { Text = "Click me" });
btn.Click += new EventHandler(btn_Click);
Controls.Add(bar = new ProgressBar { Dock = DockStyle.Bottom, Visible = false, Minimum = 0, Maximum = 100 });
worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bar.Visible = false;
if (e.Error != null)
{
Text = e.Error.Message;
}
else if (e.Cancelled)
{
Text = "cancelled";
}
else
{
Text = e.Result == null ? "complete" : e.Result.ToString();
}
btn.Enabled = true;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int count = 0; count < 100; count++)
{
worker.ReportProgress(count);
Thread.Sleep(50);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
bar.Value = e.ProgressPercentage;
}
void btn_Click(object sender, EventArgs e)
{
bar.Value = 0;
bar.Visible = true;
btn.Enabled = false;
worker.RunWorkerAsync();
}
}
You are blocking the Main UI thread. You can use a background worker to do this. You can find more details in MSDN
If you want to run your code you should put this code in a function and call this function with one thread.
public static void fun1()
{
for (int i = 0; i <= 10; i++)
{
Console.Write("This is function1");
Console.Write("\n");
}
}
Thread firstthread = new Thread(new ThreadStart(fun1));
firstthread.Start();
firstthread.suspend();//whenever you want your current control to stop.
b'caz Thread.sleep(100) will stop the whole context not that particular you want..
Answer suggested by Marc will help. Lon running operations can make your application crash or not responsive. I have a blog post related to the usage of the background worker class.
http://midnightprogrammer.net/post/Using-Background-Worker-in-C.aspx

How can a new Form be run on a different thread in C#?

I'm just trying to run a new thread each time a button click even occurs which should create a new form. I tried this in the button click event in the MainForm:
private void button1_Click(object sender, EventArgs e)
{
worker1 = new Thread(new ThreadStart(thread1));
worker2 = new Thread(new ThreadStart(thread2));
worker1.Start();
worker2.Start();
}
private void thread1()
{
SubForm s = new SubForm();
s.Show();
}
private void thread2()
{
SubForm s = new SubForm();
s.Show();
}
The code in the Subform button click event goes like this:
private void button1_Click(object sender, EventArgs e)
{
int max;
try
{
max = Convert.ToInt32(textBox1.Text);
}
catch
{
MessageBox.Show("Enter numbers", "ERROR");
return;
}
progressBar1.Maximum = max;
for ( long i = 0; i < max; i++)
{
progressBar1.Value = Convert.ToInt32(i);
}
}
Is this the right way? Because I'm trying to open two independent forms, operations in one thread should not affect the other thread.
Or is BackGroundworker the solution to implement this? If yes, can anyone please help me with that?
You do not need to run forms in separate threads. You can just call s.Show() on multiple forms normally. They will not block each other.
Of course, if you’re doing something else, like some sort of calculation or other task that takes a long while, then you should run that in a separate thread, but not the form.
Here is a bit of code that will let you create a progress bar that shows progress for a long process. Notice that every time to access the form from inside the thread, you have to use .Invoke(), which actually schedules that invocation to run on the GUI thread when it’s ready.
public void StartLongProcess()
{
// Create and show the form with the progress bar
var progressForm = new Subform();
progressForm.Show();
bool interrupt = false;
// Run the calculation in a separate thread
var thread = new Thread(() =>
{
// Do some calculation, presumably in some sort of loop...
while ( ... )
{
// Every time you want to update the progress bar:
progressForm.Invoke(new Action(
() => { progressForm.ProgressBar.Value = ...; }));
// If you’re ready to cancel the calculation:
if (interrupt)
break;
}
// The calculation is finished — close the progress form
progressForm.Invoke(new Action(() => { progressForm.Close(); }));
});
thread.Start();
// Allow the user to cancel the calculation with a Cancel button
progressForm.CancelButton.Click += (s, e) => { interrupt = true; };
}
Although I'm not 100% aware of anything that says running completely seperate forms doing completely isolated operations in their own threads is dangerous in any way, running all UI operations on a single thread is generally regarded as good practice.
You can support this simply by having your Subform class use BackgroundWorker. When the form is shown, kick off the BackgroundWorker so that it processes whatever you need it to.
Then you can simply create new instances of your Subform on your GUI thread and show them. The form will show and start its operation on another thread.
This way the UI will be running on the GUI thread, but the operations the forms are running will be running on ThreadPool threads.
Update
Here's an example of what your background worker handlers might look like - note that (as usual) this is just off the top of my head, but I think you can get your head around the basic principles.
Add a BackgroundWorker to your form named worker. Hook it up to the following event handlers:
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Executed on GUI thread.
if (e.Error != null)
{
// Background thread errored - report it in a messagebox.
MessageBox.Show(e.Error.ToString());
return;
}
// Worker succeeded.
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Executed on GUI thread.
progressBar1.Value = e.ProgressPercentage;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// Executed on ThreadPool thread.
int max = (int)e.Argument;
for (long i = 0; i < max; i++)
{
worker.ReportProgress(Convert.ToInt32(i));
}
}
Your click handler would look something like:
void button1_Click(object sender, EventArgs e)
{
int max;
try
{
// This is what you have in your click handler,
// Int32.TryParse is a much better alternative.
max = Convert.ToInt32(textBox1.Text);
}
catch
{
MessageBox.Show("Enter numbers", "ERROR");
return;
}
progressBar1.Maximum = max;
worker.RunWorkerAsync(max);
}
I hope that helps.
Try this. It runs the new Form on its own thread with its own message queues and what not.
Run this code:
new Thread(new ThreadStart(delegate
{
Application.Run(new Form());
})).Start();
Use Thread.CurrentThread.GetHashCode() to test that is runs on different thread.
It's possible to run different forms on different threads. There are two caveats I'm aware of:
Neither form may be an MDI client of the other. Attempting to make a form an MDI client of another when the forms have different threads will fail.
If an object will be sending events to multiple forms and all forms use the same thread, it's possible to synchronize the events to the main thread before raising it. Otherwise, the event must be raised asynchronously and each form must perform its own synchronization mechanism for incoming events.
Obviously it's desirable not to have any window's UI thread get blocked, but using separate threads for separate windows may be a nice alternative.

Categories