I am brand new to the idea of BackgroundWorkers, so this has me a little perplexed.
So I created a new WPF Application and created a BackgroundWorker and List class variable:
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
private List<int> tempList = new List<int>();
...
Then I use the BackgroundWorker to populate that list: (In the same class btw)
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Random r = new Random();
for (int j = 1; j <= 100; j++)
{
for (int i = 0; i < 1000; i++)
{
tempList.Add(r.Next(100));
}
...
}
}
Now here is the part that's getting me...
The code that populates that list seems to be working fine. When I step through its execution, *it behaves as I have intended until the exact moment the code exits the bw_DoWork method.* After that point, it reverts back to an empty list. I changed the list to static at one point but nothing changed.
So why is this List not persisting throughout the programs execution?
I am(was) almost certain that this is some issue with the list being allocated in different memory regions for each thread, but I simply know far too little about about BackgroundWorker and MultiThreading in general to diagnose it myself.
Any help would be appreciated.
Before you start using more expensive options such as locking or thread safe collections. Try out Threading Tasks. If they work then you have some kind of a problem with your BackgroundWorker, if they don't then your code touches the list somewhere and you'll have to trace that.. (I just think Tasks are so much easier to work with)
private void bw_DoWork()
{
Task.Factory.StartNew(
() =>
{
var r = new Random();
for (int j = 1; j <= 100; j++)
{
for (int i = 0; i < 1000; i++)
{
tempList.Add(r.Next(100));
}
//the rest of whaterver you're doing...
}
});
}
#Stephen Marsh like #Douglas said you need to wait until work is finish.
See this:
// this execute the DoWork asynchronously.
bw.RunWorkerAsync();
// asynchronously means the next line may be executed
// before the DoWork fill the list. In fact can be executed
// before the DoWork begin.
MessageBox.Show("Without wait: " + tempList.Count.ToString());
To correct you can add this line before call RunWorkerAsync:
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
and put this in any place of the MainWindows class.
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed: " + tempList.Count.ToString());
}
In my tests always the result was:
"Without wait: 0"
"Completed: 100000"
Related
I'm working with a WinForm from which all processes that I need are steered. Now I'm trying to integrate a BackgroundWorker with a ProgressBar and a cancellation button into my code. I want it to be locally around my code and not in a separate method. To test this, a new form is created with a progress bar (not active yet) and a button to stop a for-loop. However, the code is not working (and the progress bar is not even included yet). The form freezes immediately (see image) so I can't test the cancel button. The for-loop, however, is executed and "Done: " + l.ToString() is shown. How can I solve this?
void stopMeasurement(object sender, EventArgs e)
{
stopMeas = true;
}
public void testcancel() // Test method which is triggered manually
{
int l = 0;
MetingProgress metingProgress = new MetingProgress();
metingProgress.btnCancelmeting.Click += new EventHandler(stopMeasurement);
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += (sender, args) =>
{
for (int k = 0; k < 10; k++)
{
Thread.Sleep(1000);
l++;
if (worker.CancellationPending)
break;
}
MessageBox.Show("Done: " + l.ToString());
};
worker.RunWorkerAsync();
while (worker.IsBusy)
{
if (stopMeas)
worker.CancelAsync();
}
metingProgress.Dispose();
MessageBox.Show("All done");
}
The form freezes immediately
this is because you have a while loop still running on the main thread! So the form will not be responsive. This is called buisy waiting. You will not be able to call the CancelAsync method.
One solution could be to remove the while-loop and place the cancel call into the button event code:
void stopMeasurement(object sender, EventArgs e)
{
stopMeas = true;
worker.CancelAsync();
}
What you have basically done is: you created a second cancelation token. So another possibility could be to use only stopMeas to cancel the background operation:
worker.DoWork += (sender, args) =>
{
for (int k = 0; k < 10; k++)
{
Thread.Sleep(1000);
l++;
if (stopMeas)
break;
}
string mes = stopMeas ? "Done: " + l.ToString() : "Task aborted!";
MessageBox.Show(mes);
};
EDIT: also this line:
metingProgress.Dispose();
might lead to an ObjectDisposed exception. If the background process is still running and trying to update your progressbar and you already dispose the form. You should remove this line and leave it to the garbage collector.
This code is your problem:
while (worker.IsBusy)
{
if (stopMeas)
worker.CancelAsync();
}
Your GUI-Thread is in that loop until your worker is done.
You need to make your worker instance be reachable from within the EventHandler and call worker.CancelAsync() from there.
Outside this , I personally would improve the code in 2 steps:
Move the whole BackgroundWorker into the MetingProgress class and make its constructor take a delegate for the actual work implementation.
Use TAP (Task Async Pattern) , i.e. async/await Task with Progress and CancellationToken.
I try to perform an easy task in an other backgroundthread, so the UI doesn't get blocked, but it still gets blocked. Did I forget anything?
public partial class backgroundWorkerForm : Form
{
public backgroundWorkerForm()
{
InitializeComponent();
}
private void doWorkButton_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
for (int i = 0; i < 10000; i++)
{
textBoxOutput.AppendText(i + Environment.NewLine);
}
}));
}
}
}
While the textBox gets filled, the UI is blocked:
Your app wants to repeatedly send updates from the background thread to the UI. There is a built-in mechanism for this: the ProgressChanged event for the background worker. A ReportProgress call is triggered in the background, but executes on the UI thread.
I do change one thing, however. Performance can degrade with too many cross-thread calls. So instead of sending an update every iteration, I instead will batch them into 100.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
const int maxIterations = 10000;
var progressLimit = 100;
var staging = new List<int>();
for (int i = 0; i < maxIterations; i++)
{
staging.Add(i);
if (staging.Count % progressLimit == 0)
{
// Only send a COPY of the staging list because we
// may continue to modify staging inside this loop.
// There are many ways to do this. Below is just one way.
backgroundWorker1.ReportProgress(staging.Count, staging.ToArray());
staging.Clear();
}
}
// Flush last bit in staging.
if (staging.Count > 0)
{
// We are done with staging here so we can pass it as is.
backgroundWorker1.ReportProgress(staging.Count, staging);
}
}
// The ProgressChanged event is triggered in the background thread
// but actually executes in the UI thread.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0) return;
// We don't care if an array or a list was passed.
var updatedIndices = e.UserState as IEnumerable<int>;
var sb = new StringBuilder();
foreach (var index in updatedIndices)
{
sb.Append(index.ToString() + Environment.NewLine);
}
textBoxOutput.Text += sb.ToString();
}
EDIT:
This requires you set the background worker's WorkerReportsProgress property to true.
It's not important that you pass a count with the ReportProgress call. I do so just to have something and to quickly check if I can return.
One really should keep in mind about how many events are being invoked and queued up. Your original app had 10,000 cross thread invocations and 10,000 changed text events for textBoxOutput. My example uses 100 cross thread calls since I use a page size of 100. I could still have generated 10,000 changed text events for the textbox, but instead use a StringBuilder object to hold a full page of changes and then update the textbox once for that page. That way the textbox only has 100 update events.
EDIT 2
Whether or not your app needs paging is not the main deal. The biggest take away should be that the background worker really should use ReportProgress when trying to communicate info back to the UI. See this MSDN Link. Of particular note is this:
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.
Your invocation code should be outside the loop. Everything in the invoked codeblock, will be executed on the UI thread, thus blocking it.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
// do long-running task
//if (textBoxOutput.InvokeRequired)
//{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
textBoxOutput.AppendText(i + Environment.NewLine);
}));
//}
}
}
an easier way would be to do completely create your output text, and then paste the full output into the TextBox, then you only need one invocation
protected delegate void SetTextDelegate(TextBox tb, string Text);
protected void SetText(TextBox tb, string Text)
{
if (tb.InvokeRequired) {
tb.Invoke(new SetTextDelegate(SetText), tb, Text);
return;
}
tb.Text = Text;
}
and then inside your dowork
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
StringBuilder sb = new StringBuilder();
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
sb.AppendLine(i.ToString());
}
SetText(textBoxOutput, sb.ToString());
}
I'm trying to use threads and prevent the program from freezing while the thread is busy. It should show the progress (writing of 0's / 1's) and not just show the result after its done, freezing the form in the meanwhile.
In the current program I'm trying to write to a textbox, and actually see constant progress, and the form can't be affected by the tasks of the other thread.
What I have now is I can write to a textbox with a thread using invoke, but It only shows the result (Form freezes while thread is busy), and the form freezes.
Form image:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace MultiThreading
{
public partial class MultiThreading : Form
{
public MultiThreading()
{
InitializeComponent();
}
Thread writeOne, writeTwo;
private void writeText(TextBox textBox, string text)
{
if (textBox.InvokeRequired)
{
textBox.BeginInvoke((MethodInvoker)delegate()
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
});
}
else
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
}
}
private void btnWrite1_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeOne.Start();
}
private void btnWrite2_Click(object sender, EventArgs e)
{
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeTwo.Start();
}
private void btnClear1_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
}
private void btnClear2_Click(object sender, EventArgs e)
{
txtOutput2.Clear();
}
private void btnWriteBoth_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeOne.Start();
writeTwo.Start();
}
private void btnClearBoth_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
txtOutput2.Clear();
}
}
}
EDIT:
Btw for anyone wondering, I'm new to multithreading and I'm just trying to write a small program to understand the best way to do this.
I understand that my previous invoke didn't realy help because I still wasn't giving the form a chance to update, so its getting there.
Ok so running 1 thread like this works, but still running multiple threads together, won't update the form till after the thread is done.
I've added a thread.sleep() so I can try and clear while writing, to see if I can still use the form.
When writing to 1 textbox I can still clear the screen while writing.
But once I use 2 threads, I can't use the form anymore till the thread completes, and gives the output.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
(If I'm totally wrong on this I don't mind having to read through some examples/threads, I'm still trying to see what is the best way to do this, besides a backgroundworker)
EDIT 2:
I've reduced the number of invokes by reducing the amount to write, but to increase delay giving the same effect of constant writing, just reducing the load.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
EDIT 3:
Sumeet's example works using
Application.DoEvents();
(notice the s, .DoEvent doesn't work, typo probably :P), writing multiple strings simultaneously & having them show the progress and not just the result.
So Code update again :)
*Using a new button to create 5 threads that write a random number to both textboxes
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 57; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(5);
Application.DoEvents();
}));
}
}
private void btnNewThread_Click(object sender, EventArgs e)
{
Random random = new Random();
int[] randomNumber = new int[5];
for (int i = 0; i < 5; i++)
{
randomNumber[i] = random.Next(2, 9);
new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
}
}
This solution works ! Have checked it.
The problem is you keep telling the UI thread to change the Text, but never letting it have time to show you the updated text.
To make your UI show the changed text, add the Application.DoEvents line like this :
textBox.Text += text;
Application.DoEvents();
p.s. : Remove the else block of your If / Else loop, it is redundant, and also as pointed by others there is not any use of creating those 2 Threads as all they are doing is post the message on the UI Thread itself.
You're still performing a single-threaded task, just re-launching it on the UI thread if needed.
for (int i = 0; i < 500; i++){
string text = ""+i;
textBox.BeginInvoke((MethodInvoker)delegate()
{
textBox.Text += text;
});
}
The problem is that you're starting a new thread, and then that new thread is doing nothing except adding one new task for the UI thread to process that does a lot of work. To keep your form responsive you need to have time where the UI thread is doing nothing, or at least not spending a significant amount of time doing any one task.
To keep the form responsive we need to have lots of little BeginInvoke (or Invoke) calls.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
}));
}
}
By having lots of little invoke calls it allows things like paint events, mouse move/click events, etc. to be handled in the middle of your operations. Also note that I removed the InvokeRequired call. We know that this method will be called from a non-UI thread, so there's no need for it.
You're defeating the purpose of using threads.
All your thread does is tell the UI thread to execute some code using BeginInvoke().
All of the actual work happens on the UI thread.
Either you're doing data processing or you're just trying to animate the UI.
For data processing you should do all the heavy lifting on a background thread and only update the UI occasionally. In your example a TextBox is particularly troublesome in this regard, as you're adding data to the underlying data model several hundred times and the UI element (a TextBox) takes longer to render each time. You must be careful about how often to update the UI so that processing for UI updates does not overwhelm data model updates. TextBoxes are nasty like that.
In the example below, a flag set during the paint event ensures that additional UI updates aren't queued until the TextBox has finished painting the last update:
string str = string.Empty;
public void DoStuff()
{
System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread);
}
void WorkerThread(object unused)
{
for (int i = 0; i < 1000; i++)
{
str += "0";
if (updatedUI)
{
updatedUI = false;
BeginInvoke(new Action<string>(UpdateUI), str);
}
}
BeginInvoke(new Action<string>(UpdateUI), str);
}
private volatile bool updatedUI = true;
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor
{
updatedUI = true;
}
void UpdateUI(string str)
{
textBox1.Text = str;
}
On the other hand if UI animation is your goal then you probably ought to be using something other than a TextBox. It's just not designed to handle updates so frequently. There might be some optimizations to text rendering you could make for your specific use case.
You must never use a string in high volume applications. UI or not. Multi-threading or not.
You should use StringBuilder to accumulate the string. and then assign
tb.Text = sb.ToString();
I have a c# application that uses a background worker thread, and quite successfully updates the UI from the running thread. The application involves shortest path routing on a network, and I display the network and the shortest path, on the UI, as the background worker proceeds. I would like to allow the user to slow down the display through use of a slider, while the application is running.
I found this as a suggestion, but it is in vb.net, I am not clear on how to get it to work in c#.
How can the BackgroundWorker get values from the UI thread while it is running?
I can pass the value of the slider to the backgroundworker as follows:
// Start the asynchronous operation.
delay = this.trackBar1.Value;
backgroundWorker1.RunWorkerAsync(delay);
and use it within the backgroundworker thread, but it only uses the initially-sent value. I am not clear on how to pick up the value from inside the backgroundworker when I move the slider on the UI.
I have previously used multiple threads and delegates, but if it is possible to utilize the background worker, I would prefer it for its simplicity.
5/10/2012
Thanks to all for your responses. I am still having problems, most likely because of how I have structured things. The heavy duty calculations for network routing are done in the TransportationDelayModel class. BackgroundWorker_DoWork creates an instance of this class, and then kicks it off. The delay is handled in TransportationDelayModel.
The skeleton of code is as follows:
In UI:
private void runToolStripMenuItem1_Click(object sender, EventArgs e)
{
if (sqliteFileName.Equals("Not Set"))
{
MessageBox.Show("Database Name Not Set");
this.chooseDatabaseToolStripMenuItem_Click(sender, e);
}
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
delay = this.trackBar1.Value;
// pass the initial value of delay
backgroundWorker1.RunWorkerAsync(delay);
// preclude multiple runs
runToolStripMenuItem1.Enabled = false;
toolStripButton2.Enabled = false;
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (!backgroundWorkerLaunched)
{
// instantiate the object that does all the heavy work
TransportationDelayModel TDM = new TransportationDelayModel(worker, e);
// kick it off
TDM.Run(sqliteFileName, worker, e);
backgroundWorkerLaunched = true;
}
}
The TransportationDelayModel constructor is:
public TransportationDelayModel(BackgroundWorker worker, DoWorkEventArgs e)
{
listCentroids = new List<RoadNode>();
listCentroidIDs = new List<int>();
listNodes = new List<RoadNode>();
listNodeIDs = new List<int>();
listRoadLink = new List<RoadLink>();
roadGraph = new AdjacencyGraph<int, RoadLink>(true); // note parallel edges allowed
tdmWorker = worker;
tdmEvent = e;
networkForm = new NetworkForm();
}
so I have the tdmWorker, which allows me to pass information back to the UI.
In the internal calculations in TransportationDelayModel, I sleep for the delay period
if (delay2 > 0)
{
tdmWorker.ReportProgress(-12, zzz);
System.Threading.Thread.Sleep(delay2);
}
so the problem seems to be how to pass an updated slider value from the UI back to the object that is executing in the background worker. I have tried a number of combinations, sort of thrashing around, to no avail, either nothing happens or I get a message about not being allowed to access what is happening on the other thread. I realize that if I were doing all the work in the DoWork event handler, then I should be able to do things as you suggest, but there is too much complexity for that to happen.
Again, thank you for your suggestions and help.
6/2/2012
I have resolved this problem by two methods, but I have some questions. Per my comment to R. Harvey, I have built a simple application. It consists of a form with a run button, a slider, and a rich text box. The run button launches a background worker thread that instantiates an object of class "Model" that does all the work (a simplified surrogate for my TransportationModel). The Model class simply writes 100 lines to the text box, incrementing the number of dots in each line by 1, with a delay between each line based on the setting of the slider, and the slider value at the end of the line, something like this:
....................58
.....................58
......................58
.......................51
........................44
.........................44
The objective of this exercise is to be able to move the slider on the form while the "Model" is running, and get the delay to change (as in above).
My first solution involves the creation of a Globals class, to hold the value of the slider:
class Globals
{
public static int globalDelay;
}
then, in the form, I update this value whenever the trackbar is scrolled:
private void trackBar1_Scroll(object sender, EventArgs e)
{
Globals.globalDelay = this.trackBar1.Value;
}
and in the Model, I just pick up the value of the global:
public void Run(BackgroundWorker worker, DoWorkEventArgs e)
{
for (int i = 1; i < 100; i++)
{
delay = Globals.globalDelay; // revise delay based on static global set on UI
System.Threading.Thread.Sleep(delay);
worker.ReportProgress(i);
string reportString = ".";
for (int k = 0; k < i; k++)
{
reportString += ".";
}
reportString += delay.ToString();
worker.ReportProgress(-1, reportString);
}
}
}
This works just fine.
My question: are there any drawbacks to this approach, which seems very simple to implement and quite general.
The second approach, based on suggestions by R. Harvey, makes use of delegates and invoke.
I create a class for delegates:
public class MyDelegates
{
public delegate int DelegateCheckTrackBarValue(); // create the delegate here
}
in the form, I create:
public int CheckTrackBarValue()
{
return this.trackBar1.Value;
}
and the Model class now has a member m_CheckTrackBarValue
public class Model
{
#region Members
Form1 passedForm;
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;
#endregion Members
#region Constructor
public Model(BackgroundWorker worker, DoWorkEventArgs e, Form1 form)
{
passedForm = form;
}
When the background thread is launched by the run button, the calling form is passed
private void button1_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (!backgroundWorkerLaunched)
{
// instantiate the object that does all the heavy work
Model myModel= new Model(worker, e, this);
Model.m_CheckTrackBarValue = new MyDelegates.DelegateCheckTrackBarValue(this.CheckTrackBarValue);
// kick it off
myModel.Run(worker, e);
backgroundWorkerLaunched = true;
}
}
Finally, in the Model, the Invoke method is called on the passed form to get the value of the trackbar.
public void Run(BackgroundWorker worker, DoWorkEventArgs e)
{
for (int i = 1; i < 100; i++)
{
int delay = (int)passedForm.Invoke(m_CheckTrackBarValue,null); // invoke the method, note need the cast here
System.Threading.Thread.Sleep(delay);
worker.ReportProgress(i);
string reportString = ".";
for (int k = 0; k < i; k++)
{
reportString += ".";
}
reportString += delay.ToString();
worker.ReportProgress(-1, reportString);
}
}
This works as well. I kept getting an error until I made the member variable static, e.g.
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;
My questions on this solution: Are there advantages to this solution as regards to the previous version? Am I making things too complicated in the way I have implemented this? Why does m_CheckTrackBarValue need to be static.
I apologize for the length of this edit, but I thought that the problem and solutions might be of interest to others.
You have to pass the TrackBar object to the BackgroundWorker, not delay. delay doesn't change once you set it.
To simplify the needed Invoke(), you can use a helper method, such as this one:
Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);
I will assume that you are already familiarized with cross-thread invocation to update the UI. So, the solution is very simple: in your worker thread, after each iteration, invoke the UI to get the slider thumb position.
To use a backgroundworker, you add a method to the DoWork property, like this:
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
In the DoWork method, you need to check the variable where the updated delay is set.
This could be an integer field that is available on the containing Form or UI control, or it could be the TrackBar itself.
I am trying to write a simple multithreaded program in C#. It has a button pressing which creates a new label on form, and then a for loop runs displaying loop value in label. So if you press button 3 times, it will create 3 threads with 3 labels on form with loop.
When I press the button once, it works fine. But when I press it more than once to create more labels, it runs into following problems:
As soon as button is pressed more than once, it stops the loop in previous thread and runs loop of new thread. If it is multithreaded then it should not stop first loop.
When loop of second label is finished, it gives following error
Object reference not set to an instance of an object
Here is my complete code. The line which throws error is at the end "mylabel[tcount].Text = i.ToString();".
Screenshot of program: http://i.imgur.com/IFMIs.png
Screenshot of code http://i.imgur.com/sIXtc.png
namespace WindowsFormsApplication2{
public partial class Form1 : Form{
public Form1(){
InitializeComponent();
}
private int tcount = 0;
private int y_point = 0;
Thread[] threads = new Thread[5];
Label[] mylabel = new Label[5];
private void button1_Click(object sender, EventArgs e){
threads[tcount] = new Thread(new ThreadStart(work));
threads[tcount].Start();
}
private void work(){
if (this.InvokeRequired){
this.Invoke(new MethodInvoker(delegate{
mylabel[tcount] = new Label();
mylabel[tcount].Text = "label" + tcount;
mylabel[tcount].Location = new System.Drawing.Point(0, y_point + 15);
y_point += 25;
this.Controls.Add(mylabel[tcount]);
for (int i = 0; i < 10000; i++){
mylabel[tcount].Text = i.ToString();
Application.DoEvents();
}
}));
}
tcount++;
}
}
}
If it is multithreaded then it should not stop first loop.
But it is not multithreaded.
this.Invoke(new MethodInvoker(delegate{
This switches via invoker the context back to the UI Thread, so while you open a lot of threads in the background, you basically then put all the processing back into one main thread.
This:
Application.DoEvents();
Then gives other queued work a chance. Still only on the UI thread.
And finally you never parametrize the threads so they all work on the same variables. There is only one non thread save (no lock, no volatile) variable named tCount - bang.
Basically you demonstrate:
Your problem is not solvable multi threaded - any UI element manipulation HAS to happen on the UI thread (which is why you invoke) and as this is all you do you basically can not multithread.
You lack a basic understanding on how UI programs work with threads and the message pump.
You lack a basic understanding on variable scoing and access patterns between threads.
Back to reading documentation I would say.
The problem is the scope of tcount, as all threads acces the same instance of it, so as soon as the second thread starts the first thread also wirtes into the second label.
Also you invoke your whole worker method which will let it run in the UI-Thread again -> not actually multithreaded...
Your worker method should look something like this:
private void work()
{
int tIndex = tCount; //store the index of this thread
tcount++;
mylabel[tIndex] = new Label();
mylabel[tIndex].Text = "label" + tcount;
mylabel[tIndex].Location = new System.Drawing.Point(0, y_point + 15);
y_point += 25;
Invoke((MethodInvoker)delegate() { this.Controls.Add(mylabel[tIndex]); });
for (int i = 0; i < 10000; i++)
{
//doWork
Invoke((MethodInvoker)delegate() { mylabel[tIndex].Text = i.ToString(); });
}
}
Jep, you need to copy tcount to a local variable. As soon as you hit the button twice while a thread has not yet terminated, it is manipulating the second one.