I am trying my hardest to learn Cross/Multi Threading, but I am very confused on the concept.
I made a sample application which is suppose to display i on a label.text via a thread.
it's not working because I am trying to access a thread other than it was created on, I've researched a lot and I am still confused on Invoking, Delegation, etc...
Here is my code:
private void s1_Click(object sender, EventArgs e)
{
Thread Thread1 = new Thread(new ThreadStart(Start1));
Thread1.Start();
}
public void Start1()
{
for (int i = 0; i < 1000; i++)
{
displaytext("Working.........", i);
Thread.Sleep(100);
}
}
public void displaytext(string thetext, int number)
{
t1.Text = thetext + " " + number;
}
What is a good way to get this working ?
Any help is greatly appreciated.
I am learning this for the love of programming.
I am trying to access a thread other than it was created on
The actual error is accessing a Windows Forms control on a thread other than the one creating it.
The fix: use Invoke.
public void Start1()
{
for (int i = 0; i < 1000; i++)
{
t1.Invoke(() => displaytext("Working.........", i));
Thread.Sleep(100);
}
}
You gotta Invoke the function through delegate to get it to work.
private void s1_Click(object sender, EventArgs e)
{
Thread Thread1 = new Thread(new ThreadStart(Start1));
Thread1.Start();
}
public void Start1()
{
for (int i = 0; i < 1000; i++)
{
if(t1.InvokeRequired)
{
t1.Invoke(new MethodInvoker( () => displaytext("Working.........", i)));
}
else
{
displaytext("Working........", i);
}
Thread.sleep(100);
}
}
public void displaytext(string thetext, int number)
{
t1.Text = thetext + " " + number;
}
Related
I am trying to sort an array of 5 random integers in a listbox to ascending order. While creating a new thread and calling it separately, the form becomes unresponsive when I select the 'sort' button.
public partial class Form1 : Form
{
const int iSize = 5;
int[] iArray = new int[iSize];
Random rnd = new Random();
public Form1()
{
InitializeComponent();
}
Thread th;
private void btnGenerate_Click(object sender, EventArgs e)
{
for (int i = 0; i < iArray.Length; i++)
{
iArray[i] = rnd.Next(0, 5);
lbxNumbers.Items.Add(iArray[i]);
}
}
public void threadMethod()
{
bubbleSort(iArray);
foreach(int item in iArray)
{
lbxNumbers.Items.Add(item);
}
}
private void btnSort_Click(object sender, EventArgs e)
{
th = new Thread(threadMethod);
lblStatusUpdate.Text = "Sorting...";
}
private void btnClear_Click(object sender, EventArgs e)
{
lbxNumbers.Items.Clear();
}
public static void bubbleSort(int [] iArray)
{
bool isSorted = false;
int lastUnsorted = iArray.Length - 1;
while(!isSorted)
{
for(int i = 0; i < iArray.Length-1; i++)
{
if(iArray[i] > iArray[i+1])
{
swap(iArray, i, i + 1);
isSorted = false;
}
}
}
}
public static void swap(int [] iArray, int i, int j)
{
int tmp = iArray[i];
iArray[i] = iArray[j];
iArray[j] = tmp;
}
}
I am uncertain where the thread actually kicks in. The listbox can generate the array immediately and clear it, but sort makes it freeze. I am also unsure whether I need to use the background worker tool on this form.
To expound on Dour High Arch's comment.
EDIT
There are a couple of issues with your code. UI updates can only be done on the UI thread. You can accomplish this with the SychronizationContext or async/await handles it for you. Also, your bubblesort was missing an exit condition for the while loop, so it would run forever. The following demonstrates how this would work using async/await.
private async void btnSort_Click(object sender, EventArgs e)
{
lblStatusUpdate.Text = #"Sorting...";
await Run();
}
public async Task Run()
{
//This line runs asynchronously and keeps the UI responsive.
await bubbleSort(iArray);
//The remaining code runs on the UI thread
foreach (int item in iArray)
{
lbxNumbers.Items.Add(item);
}
}
public async Task bubbleSort(int[] iArray)
{
await Task.Run(() =>
{
bool isSorted = false;
while (!isSorted)
{
isSorted = true; //You were missing this to break out of the loop.
for (int i = 0; i < iArray.Length - 1; i++)
{
if (iArray[i] > iArray[i + 1])
{
swap(iArray, i, i + 1);
isSorted = false;
}
}
}
});
}
You can not act on UI, or Main thread within of another.
This line :
lbxNumbers.Items.Add(item);
which is invoked from another thread can cause a trouble.
The proper way of handling this scenario in WindowsForms would be using of
Control.Invoke method.
private void _btnOK_Click(object sender, EventArgs e)
{
_label1.Hide();
_label2.Hide();
_label3.Hide();
for(int i = 1; i <= 100; i++)
{
Thread.Sleep(5);
_circularprogressbar.Value = i;
_circularprogressbar.Update();
}
}
private void LoadingScreen_Load(object sender, EventArgs e)
{
_circularprogressbar.Value = 0;
_circularprogressbar.Minimum = 0;
_circularprogressbar.Maximum = 100;
}
}
}
This is my code. What i want to do is, i want to have a text inside the progress bar that shows the percentage of the progress from 1 to 100 percent.
what can i add to my code?
thank you
Here is what i would do:
private void _btnOK_Click(object sender, EventArgs e)
{
_label1.Hide();
_label2.Hide();
_label3.Hide();
for(int i = 1; i <= 100; i++)
{
_circularprogressbar.Value = i;
_percent_lable_name.Text = string.Format("{0}%", _circularprogressbar.Value);
_circularprogressbar.Update();
}
}
private void LoadingScreen_Load(object sender, EventArgs e)
{
_circularprogressbar.Value = 0;
_circularprogressbar.Minimum = 0;
_circularprogressbar.Maximum = 100;
}
}
See if that helps you!
Thanks
Techcraft7 :)
That Thread.Sleep(5) is blocking your entire UI thread. If you want to have your UI responsive, while the progress takes place, you need to make a separate thread for it. Something like this:
private void _btnOK_Click(object sender, EventArgs e)
{
_label1.Hide();
_label2.Hide();
_label3.Hide();
Task.Factory.StartNew(() =>
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(5);
Invoke((Action)(() =>
{
_circularprogressbar.Value = i;
_circularprogressbar.Update();
}));
}
});
}
Note that you will need t use Invoke to BeginInvoke to access UI components from inside that thread.
I am writing in C# and using .net framework 3.5. I am running through multiple loops that on each iteration build the UI and then wait for user feedback (waiting for a Pass or Fail button click). Here is what I am looking to do:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// add both button handlers to function btn_Click
for(int test = 0; test < 5; test++)
for(int variation = 0; variation < 4; variation++)
for(int subtest = 0; subtest < 3; subtest++)
{
// call function to update GUI
// may need to do stuff while at this state
// wait for user to click pass/fail button
}
}
private void btn_Click(object sender, EventArgs e)
{
// pass button pressed, inform for loop to iterate to next test
// if fail button, then stop tests
}
}
The wait inside the for loop is what gets me. Since this is single threaded I was running into issues with Sleep and do not like the idea of putting the check for a button press inside a while loop with a global variable. I tried AutoResetEvent to wait for the button click but that gave me issues too. What is the best way to handle this?
I did write the code in a way that I could try to implement it but do not think it is a very elegant solution:
public partial class Form1 : Form
{
int test;
int variation;
int subtest;
public Form1()
{
InitializeComponent();
test = 0;
variation = 0;
subtest = 0;
// update GUI
btnUpdate.Text = "Hello # " + test.ToString() + "." + variation.ToString() + "." + subtest.ToString();
}
private void btnUpdate_Click(object sender, EventArgs e)
{
subtest++;
if (test >= 5)
if (variation >= 4)
if (subtest >= 3)
{
// done case
Console.WriteLine("Tests are complete");
btnUpdate.Visible = false;
}
if (subtest > 3)
{
subtest = 0;
variation++;
}
if (variation > 4)
{
variation = 0;
test++;
}
Console.WriteLine("I was clicked");
// update button
btnUpdate.Text = "Hello # " + test.ToString() + "." + variation.ToString() + "." + subtest.ToString();
}
}
Thanks in advance!
Threads are overkill for this kind of scenario. You just need to do some code cleanup and refactoring and it will immediately look much nicer and logical.
public class LoopCounter
{
int test = 0;
int variation = 0;
int subtest = 0;
const int MAX_TEST = 5;
const int MAX_VARIATION = 4;
const int MAX_SUBTEST = 3;
public int Test {get{return test;}}
public int Variation {get{return variation;}}
public int Subtest {get{return subtest;}}
public bool DoNext()
{
if (test >= MAX_TEST) // test for end all cycling
return false;
subtest++;
if (subtest < MAX_SUBTEST)
return true;
subtest = 0;
variation ++;
if (variation < MAX_VARIATION)
return true;
variation = 0;
test++;
if (test < MAX_TEST)
return true;
return false;
}
}
Then, you can easily use this code as follows:
public partial class Form1 : Form
{
LoopCounter counter = new LoopCounter();
public Form1()
{
InitializeComponent();
UpdateUI();
}
private void UpdateUI()
{
// update GUI
btnUpdate.Text = "Hello # " + counter.Test.ToString() + "." + counter.Variation.ToString() + "." + counter.Subtest.ToString();
}
private void btnUpdate_Click(object sender, EventArgs e)
{
Console.WriteLine("I was clicked");
if (counter.DoNext())
{
UpdateUI();
}
else
{
// done case
Console.WriteLine("Tests are complete");
btnUpdate.Visible = false;
}
}
}
Just use a separate thread to UpdateUI and block it while button not pressed:
public partial class Form1 : Form
{
AutoResetEvent are = new AutoResetEvent(true);
public Form1()
{
InitializeComponent();
_updateGUI = new UpdateGUIDelegate(UpdateGUI);
Task.Factory.StartNew(() => BuildTest());
}
private void BuildTest()
{
for (int test = 0; test < 5; test++)
for (int variation = 0; variation < 4; variation++)
for (int subtest = 0; subtest < 3; subtest++)
{
are.WaitOne();
if (this.InvokeRequired)
this.Invoke(_updateGUI, test, variation, subtest);
else
UpdateGUI(test, variation, subtest);
}
}
delegate void UpdateGUIDelegate(int test, int variation, int subtest);
private UpdateGUIDelegate _updateGUI;
private void UpdateGUI(int test, int variation, int subtest)
{ }
private void btn_Click(object sender, EventArgs e)
{
are.Set();
}
}
I am trying to update an array of Labels which are on a form from a backgroundworker. Here is my code:
for (int i = 0; i < 6; i++)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
arrLabel[i].Text = values[i].ToString();
});
}
else
{
arrLabel[i].Text = values[i].ToString();
}
}
This does not work, but if I try to change text property of each label instead of the array, it works. How can I fix this? Also is there a shorter/better way of updating form controls from backgroundworkers than what I am doing for every single control on my form?
Edit: here is how I defined the array:
private Label[] arrLabel = new Label[6];
and here is the function that I call to assign the array:
private void makeLabelArrays()
{
for (int i = 0; i < 6; i++)
{
arrLabel[i] = (Label)Groupbox1.Controls["label" + (i + 1).ToString()];
}
}
I'm assuming what some of your code looks like; I may be wrong.
You can use the ReportProgress() method to send two pieces of information back to the UI thread - the percentage of completeness (doesn't really apply in your case so I specified 0) and some piece of data (any object you want, just a number in this case).
Then you can get the data in the ProgressChanged event and execute code that touches the UI.
private List<Label> arrLabel = new List<Label>();
private List<string> values = new List<string>();
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var bw = (BackgroundWorker)sender;
for (int i = 0; i < 6; i++)
bw.ReportProgress(0, i);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var currentIndex = Convert.ToInt32(e.UserState);
arrLabel[currentIndex].Text = values[0].ToString();
}
Make sure you enable reporting progress, as it's disabled by default.
backgroundWorker1.WorkerReportsProgress = true;
try the following
private delegate void delegateAssignText();
public void AssignText()
{
if (this.InvokeRequired)
{
Invoke(new delegateAssignText(AssignText));
return;
}
for (int i = 0; i < 6; i++)
{
arrLabel[i].Text = values[0].ToString();
}
}
The following is my background worker thread
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread t1 = new Thread(Thread1);
t1.Start();
Thread t2 = new Thread(Thread2);
t2.Start();
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
}
}
Thread1 code is as follows
static void Thread1()
{
int nofiles=0;
int returned = checkforfolderthread(1);
int startvalue = 0;
int stopvalue = 5000;
if (returned == 1)
{
nofiles = countfiles();
startvalue = startvalue + (nofiles - 1) * 1000;
stopvalue = stopvalue - startvalue;
}
repeat(startvalue, stopvalue,1,nofiles-1);
}
Function called from a thread is as follows
static void repeat(int ini, int fin, int threadno, int startadd)
{
int i, j;
for (j = ini; j < ini + fin; j += 1000)
{
StringBuilder sb = new StringBuilder();
for (i = j; i < j + 1000; i += 100)
{
WebClient wc = new WebClient();
string add = System.String.Format("http://www.colourlovers.com/api/colors/new?numResults=100&resultOffset={0}", i);
try
{
string tobeadded = wc.DownloadString(add);
sb.AppendLine();
sb.Append(tobeadded);
}
catch (Exception)
{
break;
}
}
string folderpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filename = System.String.Format("DownloadPalette\\Thread{0}\\color{1}.xml",threadno,startadd);
string location = Path.Combine(folderpath, filename);
File.WriteAllText(location, sb.ToString());
startadd = startadd + 1;
}
}
What I would want to do is continuously update a progressbar after each for i loop is completed.
But I cannot access the progressbar from this function running in the background thread.
Please Help me
You miss this method..
// Back on the 'UI' thread so we can update the progress bar
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
progressBar1.Value = e.ProgressPercentage;
}
According on this reference : BackgroundWorker and ProgressBar demo
You should use invoke, as described here: http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
First create a method to update the progressbar(create it on the main thread GUI):
private void UpdateBar()
{
//your code to update
}
Then create a delegate and pass you method to it(this code is also for main thread GUI):
private delegate void UpdateProgressBarDelegate();
private UpdateProgressBarDelegate UpdateProgressBarDelegate_Object;
UpdateProgressBarDelegate_Object = new UpdateProgressBarDelegate(this.UpdateBar);
Now update it from another thread like this:
progressbar.Invoke(UpdateProgressBarDelegate_Object);
Here we are calling the delegate object which will call UpdateBar method on GUI thread with a safe thread call.
If you need to update more than just the progressbar value you could call a method and checking if an invoke is required. An invoke is required if you want to access a UI object from a separate thread.
private void updateProgress(object sender, int count, int total)
{
if (base.InvokeRequired)
{
base.Invoke(new ProcessCountHandler(this.updateProgress), new object[] { sender, count, total });
}
else if (count <= this.progressBar1.Maximum)
{
this.progressBar1.Value = count;
this.CompletedCount.Text = count.ToString("N0") + " of " + total.ToString("N0");
}
}