Multithreading on Windows Forms Unresponse Main Form - c#

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.

Related

Avoid blocking the form without using Application.DoEvents() [duplicate]

This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
WinForm Application UI Hangs during Long-Running Operation
(3 answers)
Closed 3 years ago.
My program displays the time in a timer event and a button starts a function that keeps reading the content of a file until it reaches 50 lines.
The test file is created by a different process that once in a while appends some lines to it.
How can I modify the program to avoid blocking the form during
execution ?
The difference compared to WinForm Application UI Hangs during Long-Running Operation is that the functions called have to update some elements of the form.
And I don't want to use Application.DoEvents(),
I have hundreds of lines of Application.DoEvents() in my programs and sometimes they create a mess.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
void Timer1Tick(object sender, EventArgs e)
{
UpdateTime();
}
void UpdateTime()
{
DateTime dt = DateTime.Now;
textBox1.Text = dt.ToString("hh:mm:ss");
}
void BtnRunClick(object sender, EventArgs e)
{
int nlines = 0;
while(nlines < 50) {
nlines = listBox1.Items.Count;
this.Invoke(new Action(() => ReadLinesFromFile()));
Thread.Sleep(1000);
}
}
void ReadLinesFromFile()
{
string sFile = #"D:\Temp1\testfile.txt";
string[] lines = File.ReadAllLines(sFile);
listBox1.Items.Clear();
foreach(string line in lines) {
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
}
}
Asynchronous approach for IO operation will execute all operations on the same UI thread without blocking it.
private async void BtnRunClick(object sender, EventArgs e)
{
int nlines = 0;
while(nlines < 50) {
nlines = listBox1.Items.Count;
await ReadLinesFromFile();
await Task.Delay(1000);
}
}
private async Task ReadLinesFromFile()
{
var file = #"D:\Temp1\testfile.txt";
string[] lines = await ReadFrom(file);
listBox1.Items.Clear();
foreach(string line in lines) {
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
}
private async Task<string[]> ReadFrom(string file)
{
using (var reader = File.OpenText(file))
{
var content = await reader.ReadToEndAsync();
return content.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
}
}
You need only to invoke the ui updates:
void BtnRunClick(object sender, EventArgs e)
{
new Thread(Run).Start();
}
void Run()
{
int nlines = 0;
while (nlines < 50)
{
nlines = listBox1.Items.Count;
ReadLinesFromFile();
Thread.Sleep(1000);
}
}
void ReadLinesFromFile()
{
string sFile = #"D:\Temp1\testfile.txt";
string[] lines = File.ReadAllLines(sFile);
listBox1.InvokeOnUIThread(() => {
listBox1.Items.Clear();
foreach (string line in lines)
{
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
});
}
Add this class to your project:
public static class ControlExtensions
{
public static void InvokeOnUIThread(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}
}
All you just need is Async and Await markers to achieve this.
This you can apply to problems which have a long running operation that continuously updates your UI.
The below snippet continuously updates TextBox.Text in a loop, giving it the appearance of a timer. LongRunningProcess() simulates a time taking action
async Task LongRunningProcess()
{
await Task.Delay(1000);
}
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
DateTime dt = DateTime.Now;
textBox1.Text = dt.ToString("hh:mm:ss");
await Task.Run(() => LongRunningProcess());
}
}
If you want to know more about Asynchrnous programming in C# you can
refer to below article by Stephen Cleary who is THE Authority in this
field
https://blog.stephencleary.com/2012/02/async-and-await.html

How to continue through a UI with multiple loops and waiting for user feedback?

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();
}
}

Updating array of labels from backgroundworkers

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();
}
}

Progressbar update from background thread

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");
}
}

Cross-Threading, Accessing Thread other than it was created for

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;
}

Categories