C# Form MultiThreading, Need a tricky solution - c#

I have a problem about using multithreading in c#. I have a complex computing thing that must run on the main thread means it must run on the main window.
My program runs like this :
First it will open a new window and user can input parameter
Then it will run a complex computing that must run in the main thread ( i can't change this, i'm using library that prevent call the computing thing in different thread, if i'm using different thread it will come with an exception).
When the simulation run, it will also open a new window that monitoring the computing, it will also plot the graphic for it, and i need it to be real time.
Problem: Since the long time complex computing is running on the main thread, the monitoring window will be hang and not responding. Is there any tricky way to solve this? Can i use different thread to run the monitoring window? the main form is okay for not responding, but not okay for monitoring window. Here i put some code that resembles my program, not my real program, but the flow is the same.
Main Form(main thread that will run the computing thing,and input parameter from user)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
compute c = new compute();
c.computing();
}
}
Graph Form (Monitoring Graph that will plot chart, needed to be real time)
public graphic()
{
InitializeComponent();
}
private void label1_Click(object sender, EventArgs e)
{
}
public void updateLabel(int count)
{
label1.Text = "Current i value: " + count;
}
Compute class (doing the computing thing)
class compute
{
delegate void writeToForm(int i);
graphic g = new graphic();
public compute()
{
g.Show();
}
public void computing()
{
//THIS PART MUST RUN ON THE MAIN THREAD, DON'T MAKE IT ON THE OTHER THREAD
//IT IS A MUST
int count = 0;
//THIS IS THE LONG TIME COMPUTING THING
for (int i = 0; i < 100; i++)
{
count += i;
updateLabel(count);
Thread.Sleep(2000);
}
}
private void updateLabel(int count)
{
if (g.InvokeRequired)
{
g.Invoke(new writeToForm(updateLabel), new object[] { count });
return;
}
g.updateLabel(count);
}
}
I can't do anything about the method must run in the main thread, i'm using library that prevent its method to be ran from other method, thanks..

If it is must for you to perform computation in the main thread, modify your code as below
public void computing()
{
//THIS PART MUST RUN ON THE MAIN THREAD, DON'T MAKE IT ON THE OTHER THREAD
//IT IS A MUST
int count = 0;
//THIS IS THE LONG TIME COMPUTING THING
for (int i = 0; i < 100; i++)
{
count += i;
updateLabel(count);
Thread.Sleep(2000);
Application.DoEvents(); //tell windows to process pending message.
}
}

A possible workaround would be to write your monitoring form in another program, which can be launched by your main program before the computing method is executed. Yes, your main form will still hang (but you say this isn't a problem?).
You could communicate with your main application, possibly by opening a local TCP connection (which can run on a different thread without a problem). This could send commands to the monitoring application (or any other connected TCP clients if you wish), which would receive these and use the Control.Invoke method to update the UI.

You could try and call Application.DoEvents after updating the graph in the second window. This processes all window messages, which should allow your second window to be updated.

Related

Status Listbox update problem using a Background Worker in C#

I'm trying to use a background worker to update a listbox used for a status window in my Form in C#. It doesn't appear to work properly when the addToStausLog() method is called from another class outside of the MyForm class even though I pass an instance of the form to the other class that's calling the addToStatusLog update member. Instead the update doesn't happen until the class member finished and returns back to the MyForm class. Maybe there's a better a approach to creating real-time status windows that will run from any class that MyForm is passed into. I'm new to worker threads, so could someone review and let me know what I might be doing wrong or could improve on.
public MyForm()
{
InitializeComponent();
// Setup background task to update listbox status so UI is unaffected
_lListBoxQue = new List<string>();
bw_listBoxBGWorker = new BackgroundWorker();
bw_listBoxBGWorker.DoWork += (o, args) => LstbxThread_doWork();
bw_listBoxBGWorker.RunWorkerCompleted += (o, args) => LstbxThread_completed();
}
private void LstbxThread_doWork()
{
System.Threading.Thread.Sleep(100);
}
private void LstbxThread_completed()
{
// Update listbox
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
}
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
if( !bw_listBoxBGWorker.IsBusy ) // background not busy?
bw_listBoxBGWorker.RunWorkerAsync(); // update listbox in back ground task
System.Threading.Thread.Sleep(100);
return sMsg;
}
This is the member that calls another class which attempts to call the addToStatusLog several times during the process, but the updates to the listbox don't happen until the MyClass(this).updateDB() finishes. I need to see real-time updates as the updateDB() function is running. There has to be a way to make this work, I'm hoping...
private void btnUpdateDB_Click(object sender, EventArgs e)
{
if (_bIsUpdateEventRunning == false ) // is event not busy?
{
_bIsUpdateEventRunning = true;
new MyClass(this).updateDB();
_bIsUpdateEventRunning = false;
}
}
Example of class called to update the form listbox.
Public class MyClass{
private MyForm _pForm;
public MyClass(MyForm pForm){ _pForm= pForm; }
public void updateDB(){
_pForm.addToStatusLog("Hello World");
}
}
Updated Fix w/o background worker:
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
Application.DoEvents();
return sMsg;
}
Thread.Sleep is not the answer here. What you likely need is Application.DoEvents. This processes all messages currently waiting in the Windows message queue.
Thread.Sleep just tells the thread to go to sleep for the number of milliseconds you specify. If your background worker is running on the UI thread, you're putting the UI thread to sleep and it's effectively comatose. (Important: All Windows forms run on the UI thread.)
There are, of course, alternative designs that involve spinning up separate threads of execution. But these have their own issues, and you should be mindful of them before running blindly down that path.

How to implement a popup window with a counter

I would like to implement a simple popup window in Windows Forms, which will show a simple timer to the user while some slow-running process is executing. The premise is simple; show to the user that something is indeed going on and the application is not frozen. Note that this slow-running process is not a loop, nor is it something that I can tap into.
What I want is a simple popup window, showing some message along the lines "Elapsed time: x seconds", where x is incremented every second.
The basic concept is the following:
public void test()
{
//Some code which does stuff
//Popup window with counter
//Perform long running process
//Close popup window with counter
//Some other code which does other stuff
}
I tried to do it using various ways, including background workers, threads, and of course timers. But I did not manage to make it work as I wanted. And I would prefer not to post any of my code so as not to "lead" the responses to a specific way of doing this.
So what would be the best way to do this work?
Thanks.
UPDATE:
In reply to some comments, since I cannot paste any code in the replies section, I'm editing my original question to accomodate this. One of the implementations that I tried is to spawn the popup window in a separate thread. Although I got no runtime errors, the popup window did not refresh correctly. It indeed poped-up, but no text would show within it, and the counter would not refresh. Here's the code:
private void test()
{
frmProgressTimer ofrmProgressTimer = new frmProgressTimer(); //Instance of popup Form
System.Threading.Tasks.Task loadTask = new System.Threading.Tasks.Task(() => ProgressTimer(ofrmProgressTimer));
loadTask.Start();
//Perform long running process
System.Threading.Tasks.Task cwt = loadTask.ContinueWith(task => EndProgressTimer(ofrmProgressTimer));
}
private void ProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Show();
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.startTimer();
}));
}
private void EndProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
}
And here's my popup form code:
public partial class frmProgressTimer : Form
{
private int counter = 0;
private Timer timer1;
public frmProgressTimer()
{
InitializeComponent();
timer1 = new Timer();
timer1.Interval = 1000;
timer1.Tick += new EventHandler(timer1_Tick);
}
public void startTimer()
{
timer1.Start();
}
public void stopTimer()
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
counter += 1;
labelText.Text = counter.ToString();
}
}
This is actually quite easy to do. Create your dialog, define your long running operation to take place in a non-UI thread when it is shown, add a continuation to that operation which closes the dialog when the task finishes, and then show the dialog.
MyDialog dialog = new MyDialog();
dialog.Shown += async (sender, args) =>
{
await Task.Run(() => DoLongRunningWork());
dialog.Close();
};
dialog.ShowDialog();
The code to have the ticking over time should be entirely contained within the dialog, and based on the question it seems you already have that well under control with a simple Timer.
Make a new form, which will pop up, and show a timer. That way it won't be interrupted with all the work on your main form, and the timer will work continuously.
Remember when showing a new from to use newForm.ShowDialog() not newForm.Show(). Your can google the differences
I would simply start your work on a separate thread. Launch a modal form with your timer output. To display the timer use an actual timer instance set to update every second. When the timer event fire update your dialog.
Finally once you're thread completes close the dialog so your main form is active again.
First of all you need to make it not closeable by the user (as if modal dialogs weren't annoying enough) but closeable by your code. You could accomplish this by subscribing to the FormClosing event of the form. Let's say your popup form's name is Form2:
private bool mayClose = false;
public void PerformClose()
{
this.mayClose = true;
this.Close();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!this.mayClose)
e.Cancel = true;
}
Create a Timer, provide a Tick event handler, enable it and set its Interval to 500 milliseconds:
Create a label to host your desired text. Let's call it label1.
Within and surrounding your Tick event handler do something like this:
private DateTime appearedAt = DateTime.UtcNow;
private void timer1_Tick(object sender, EventArgs e)
{
int seconds = (int)(DateTime.UtcNow - this.appearedAt).TotalSeconds;
this.label1.Text = string.Format(#"Ellapsed seconds: {0}", seconds);
}
Make sure your long running process is happening on a background thread, not on the GUI thread.
Say your long running process can be thought of as the execution of a method called MyProcess.
If that is the case, then you need to call that method from a secondary thread.
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
Show the form right before the long running process starts. This can be done in any of the 2 places (marked in the previous section of code) called PLACE1 or PLACE2. If you do it in PLACE2 you will have to marshal a call back to the GUI thread in order to be able to interact with the WinForms framework safely. Why am I bringing this up ? It's because maybe the long running process is not started from within the GUI thread at all and you absolutely need to do this.
Close the form right after the long running process finishes. This can be done only in PLACE3 and you absolutely need to marshal a call.
To wrap the earlier 2 bullets and the answer, you could do this:
private void DoIt()
{
Form2 form2 = new Form2();
Action showIt = () => form2.Show();
Action closeIt = () => form2.PerformClose();
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
form2.BeginInvoke(showIt);
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
form2.BeginInvoke(closeIt);
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
}
Finally I've managed to resolve this in the most simplistic manner. And it works like a charm. Here's how to do it:
//Create an instance of the popup window
frmProgressTimer ofrmProgressTimer = new frmProgressTimer();
Thread thread = new Thread(() =>
{
ofrmProgressTimer.startTimer();
ofrmProgressTimer.ShowDialog();
});
thread.Start();
//Perform long running process
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
You can see the code for the popup window in the original post/question, with the only difference that the tick function changes the label text as:
labelText.Text = string.Format("Elapsed Time: {0} seconds.", counter.ToString());
Thank you to everybody for trying to help me out.

How to use a BackgroundWorker to update multiple labels?

This is a follow up question to Updating a dialog from another form (The code and screenshots can be found there)
To solve my GUI hanging problem I received 2 recommendations:
Using Application.DoEvents()
Using a BackgroundWorker
The DoEvents() approach works, however it has been pointed out that I should not use it. Indeed, I notice that the GUI updates correctly but is unresponsive for short times.
That's why I want to use a BackgroundWorker and have read up on it.
I don't understand how I would implement it so that it can be used to update the 4 labels in my example code separately, though.
I want to show the progress (and update 4 dialog labels) as the program successfully finishes one job. The BackgroundWorker has only 1 DoWork() though. I have tried to use the e.Argument of the DoWorkEventArgs to differentiate between the different update methods but that attempt had failed.
public partial class BackgroundWorkerImportStatusDialog : Form
{
private BackgroundWorker dialogWorker = new BackgroundWorker();
private string path;
private string clientName;
public BackgroundWorkerImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
this.path = path;
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.FileStatus);
}
public void updatePrintStatus()
{
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.PrintStatus);
}
public void updateImportStatus(string clientName)
{
this.clientName = clientName;
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.ImportStatus);
}
public void updateArchiveStatus()
{
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.ArchiveStatus);
}
private void updateLabels(object sender, DoWorkEventArgs e)
{
MessageBox.Show(e.Argument.ToString());
if ((UpdateComponent) e.Argument == UpdateComponent.FileStatus)
{
t_filename.Text = path;
}
if ((UpdateComponent) e.Argument == UpdateComponent.PrintStatus)
{
t_printed.Text = "sent to printer";
}
if ((UpdateComponent) e.Argument == UpdateComponent.ImportStatus)
{
t_client.Text = clientName;
}
if ((UpdateComponent) e.Argument == UpdateComponent.ArchiveStatus)
{
t_archived.Text = "archived";
}
}
public enum UpdateComponent { FileStatus, PrintStatus, ImportStatus, ArchiveStatus}
And I can't imagine having 4 BackgroundWorkers for this pretty trivial dialog is the solution.
As I understand your question, you want to have your dialog form inform the user about 4 different aspects of your application running:
printing status
file status
import status
archiver status
Background worker could be used to periodically check each one. You may advanced progressbar by 25% after status of each operation is checked (and update your UI with appropriate information).
You may also try async programming - i.e. just start the operation, and lets your application continue. When the operation completes, your application will be notified, and could update information on the form.
Depending on the .NET framework you're using you may use async and await (avaialble since .NET 4.5 / C# 5 - async & await on MSDN) or classic approach to asynchronous programming.
Edit:
I am not sure that BackgroundWorker is the best solution in this situation. I can imagine having something like:
BackhgroundWorker checking things just once - i.e. check printing status once, file status once, import status once, archiver status once. This may sound silly, but it could be user behavior driver - i.e. explicitly launched when user clicks or invokes this mechanism any other way. ProgressBar could be put on the application's statausbar, so that user knows that 'application is actually doing something'.
Previous approach could be improved a bit - you never actually finish your job in BackgroundWorker - instead inside your main method you just have an infinite loop. This will allow you to check things periodically. In this approach there is no point in increasing the progress.
Sample for the second approach:
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
CheckPrintingStatus();
CheckFileStatus();
CheckImportStatus();
CheckArchiverStatus();
System.Threading.Thread.Sleep(5000); // sleep for 5 seconds
}
}
}
There is a question if this solution (second approach) is better than having a thread created explicitly. You could think of creating 4 different threads, so that each could check something else. This would be a bit heavier on the OS, but on the other hand you can set different sleep times for every operation.
If you go for bare threads - you may want to use ThreadPool instead of creating threads explicitly.

control.invoke() issue

Im trying to update a progress bar while doing some data type checks on a separate thread and there seems to be a delay between what value the progress bar is at and the value which is actually show.
The following code is executed by the non-GUI thread and is used to raise the event.
protected virtual void OnUpdateProgressBar(object sender, ProgressBarEventArgs e)
{
EventHandler<ProgressBarEventArgs> TempHandler = UpdateProgressBar;
//Avoid possible race condition.
if (TempHandler != null)
{
TempHandler(this, e);
}
}
I have created a separate class for updating the progress bar and when i create an instance of it, i pass a reference to the progress bar. Below is the entire class.
public class ProgressBarChanged
{
ProgressBar statusBar;
public ProgressBarChanged(ProgressBar pb)
{
statusBar = pb;
statusBar.Value = 0;
}
public ProgressBarChanged()
{
}
public void subscribeToEvent(DataVerification test)
{
test.UpdateProgressBar += new EventHandler<ProgressBarEventArgs>(incrementPB);
}
public void incrementPB(object sender, ProgressBarEventArgs e)
{
Action action = () =>
{
if (e.CurrentRow == e.FinalRow - 10)
{
int i = 5;
}
statusBar.Maximum = e.FinalRow;
statusBar.Value = e.CurrentRow;
};
if(statusBar.InvokeRequired)
statusBar.Invoke(action);
else
action();
}
}
I have uploaded a screen shot showing the progress bar and the actual values.
Any ideas???
Thanks
The progessbar is a simple feedback to the user, not a piece of exact instrumentation. It's a pacifier.
It also incorporates it's own async logic to update the screen (independent of the message loop). This makes that it may run a little behind.
What's the big deal?
To get more accurate results, divide your range into < 100 segments and do fewer updates.
A delay is pretty normal. After all, invoking a method in the UI thread means Windows will dispatch a message and if your thread is fast enough (and CPU consuming) then it'll appear faster than UI.

Thread issue in C#

I am trying to generate a random fruit and display it on GUI in a label. I am using this code to do it.
partial class Form1 : Form
{
int MagicNumber = 0;
List<string> NameList = new List<string>();
Random r = new Random();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
NameList.Add("Apples");
NameList.Add("Pears");
NameList.Add("Oranges");
NameList.Add("Bananas");
NameList.Add("Kiwi");
for (int i = 0; i < 8; i++)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
label1.Text = NameList[MagicNumber];
Thread.Sleep(1000);
}
}
private void Display()
{
MagicNumber = r.Next(5);
}
}
The problem is the fact that in GUI i see only the last result of fruits choice and not how they are skipped from an iteration to other. I thought that this code will give me the possibility to see how fruits changes until the last was chosen , when i is 8.
Please if you have an idea why this code is not displaying how the fruits are chosen in label give me a hand !
Thanks.
You seem to be confusing timers and threads. In this case, I think what you want is a timer; specifically, System.Windows.Forms.Timer. You might do something like this:
partial class Form1 : Form
{
Timer timer = new Timer();
private void button1_Click(object sender, EventArgs e)
{
int i = 0;
timer.Tick += (s, e) =>
{
if (i < 8)
{
label1.Text = nameList[r.Next(5)];
i++;
}
else
timer.Stop();
};
timer.Interval = 1000;
timer.Start();
}
}
The idea is that you set a timer to tick once a second, and then each time it ticks, you change the label and increment the counter until it reaches 8 -- at which point it stops. You always want to make sure you call Start() after you've set Tick and Interval; otherwise, under some strange circumstances, the timer might tick before you have a chance to change the settings.
Alternatively, you could use threading and Sleep(), in which case it might look like this:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
}
private void Display()
{
for(int i = 0; i < 8; i++)
{
label1.Text = NameList[r.Next(5)];
Thread.Sleep(1000);
}
}
Thread.Sleep() always sleeps the thread that it's called from -- so maybe this is what you meant to do.
However, this might throw a thread synchronization exception -- Forms prevents you from accessing UI controls from another thread, since it might be in an invalid state (i.e. in the middle of rendering or doing something else that's volatile). System.Windows.Forms.Timer actually runs on the UI thread, so it's easier to manage.
Your approach is flawed, but you may want to understand what is going on in your code, as it may help you find a better approach:
for (int i = 0; i < 8; i++)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
label1.Text = NameList[MagicNumber];
Thread.Sleep(1000);
}
You are looking through, creating eight threads every time the button is clicked. Do you have a reason to create eight threads? If so, you may want to create them once, inside your init function and reuse them.
Then there is a race here in that your threads may not have had time to change MagicNumber before it is used, as the loop starts the threads then immediately changes the text, before going to sleep.
The sleep is another problem, as you haven't gotten off of the main (event) thread, so the text isn't changed until you exit that event handler.
If you want to see the text changing, then you will need to get off of the main thread, and in a second thread go through and do the loop of eight.
Then, you can put that thread to sleep, and since the main thread was free to make the change you will see it.
Here is an article from MS that is a bit dated, but the basic idea should help you:
http://msdn.microsoft.com/en-us/magazine/cc188732.aspx
Now you can use lambda expressions for your threads, as shown here:
http://www.rvenables.com/2009/01/threading-tips-and-tricks/
Just call Application.DoEvents(); after assigning text to label - that will refresh UI.
BTW I don't understand why you are using threads to generate random numbers
The problem is that when you execute an event handler or a function called from it, the changes are rendered at the end. Try changhing the label text inside the thread where you get the random number. You also have to set the CheckForIllegalCrossThreadCalls property to false in the form constructor.
Your observed problem of the form not refreshing is due to your function blocking the GUI thread and preventing a redraw of the window while its running. And it's continuously running for 8 seconds. The GUI thread needs to handle messages to allow a window to be redrawn.
But apart from what you observed it has has at least two theoretical problems related to threading:
The read of MagicNumber isn't volatile, so the compiler may read it only once and cache the result. It probably won't do that in practice since the code between each reading of the variable is so complicated that it can't guarantee that they won't affect the variable.
r.Next isn't threadsafe. So calling it from two different threads at the same time can corrupt the Random instance. Won't happen in practice either since the delay is so long that one thread will most likely have finished before the next one starts.
There is a much better way to choose a random item:
label1.Text = NameList.OrderBy(f => Guid.NewGuid()).First();
Randomizing on different threads is a bad idea in of itself.

Categories

Resources