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());
}
Related
I have some controls on the form of the Windows Forms application and I need to update its' texts at run-time from several threads.
Is it safe to just call BeginInvoke method like this:
BeginInvoke((MethodInvoker)delegate()
{
this.label.Text = "Some text";
});
from several threads at the same time? Should I do any additional synchronization in this case? Will it be processed by the same thread one by one and is this order guaranteed?
Thanks in advance.
Calling BeginInvoke puts the delegate on to the message queue to be processed by the UI thread, it will process the queue handling the messages one by one. So no, you do not need to do any additional synchronization (as long as the delegate is not accessing any resources that can't be accessed from the UI thread).
As for order, it is not guaranteed they will be processed in order but in practice most of the time the delegates will be processed in the order they where put in to the queue.
To address the question in the comments, instead of using multiple BeginInvoke calls you should be able to get away with just one.
You never really explained what your animation was so I am going to assume it is going to be that this.label will swap between ., .. and ... then you store the result text in this.label when you are done.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
animationTimer = new System.Windows.Forms.Timer();
animationTimer.Interval = 500;
animationTimer.Tick += animationTimer_Tick;
}
private System.Windows.Forms.Timer animationTimer;
private int dots = 0;
void animationTimer_Tick(object sender, EventArgs e)
{
//Make 1, 2, or 3 dots show up. This runs on the UI thread so we don't need to invoke.
this.label.Text = new String('.', dots + 1);
//Add one then reset to 0 if we reach 3.
dots += 1;
dots = dots % 3;
}
private void button1_Click(object sender, EventArgs e)
{
animationTimer.Start();
Task.Run(() => DoSomeSlowCalcuation());
}
private void DoSomeSlowCalcuation()
{
Thread.Sleep(5000);
this.BeginInvoke((MethodInvoker)delegate()
{
//We stop the timer before we set the text so the timer will not overwrite it.
animationTimer.Stop();
this.label.Text = "Some text";
});
}
}
This code is just a example to get my point across, if I where doing this I would use async/await for the button click and not use BeginInvoke at all.
private async void button1_Click(object sender, EventArgs e)
{
animationTimer.Start();
var result = await Task.Run(() => DoSomeSlowCalcuation());
animationTimer.Stop();
this.label.Text = result;
}
private string DoSomeSlowCalcuation()
{
Thread.Sleep(5000);
return "Some text";
}
I have made a simple gui in C# wpf, (sorry I can't show the GUI because my reputation is below 10)
It's consist of richtextbox and some other controls. Umm... this application will read a file then display the file contents to richtextbox line by line while reading the file using background worker. The function that read the file is like this :
public int parse_persoFile2(string fname, BackgroundWorker worker, DoWorkEventArgs e)
{
if (fname == null) return -1;
System.IO.StreamReader ifs;
ifs = new System.IO.StreamReader(fname);
int max = (int)e.Argument;
int p = 0;
while (ifs.Peek() != -1)
{
string tempData = ifs.ReadLine();
if (tempData.Contains("CMD=5107") || tempData.Contains("CMD=5106") || tempData.Contains("CMD=5102"))
{
//field.AppendText(tempData.Remove(tempData.LastIndexOf('\\')).Remove(0, 4) + "\r\n");
//field.AppendText("--------\r\n");
//System.Threading.Thread.Sleep(500);
string data = tempData.Remove(tempData.LastIndexOf('\\')).Remove(0, 4) + "\r\n";
worker.ReportProgress(p, data);
}
p++;
}
worker.ReportProgress(100);
return 0;
}
As we can see, I'm using backgroundworker in this function to get the string readed from file then send that string to reportprogress in order to be displayed in richtextbox. As a note that persoFile2 function is made from another object in my program... :-)
Then for the rest, I have made the doWork function, worker_progressChanged, and worker_RunWorkerCompleted to make backroundWorker works correctly. Those codes are like this :
private void doWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wrk = sender as BackgroundWorker;
parser.parse_persoFile2(fileName, wrk, e);
}
private void proggChanged(object sender, ProgressChangedEventArgs e)
{
if(e.UserState != null)
mRTB.AppendText(e.UserState.ToString());
}
private void completed(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Ok....");
}
Umm.... When I run this program, it looks that my richtextbox is not prints the string line by line from the file, but it prints it just once at the end... :-3, .. Nah that's my real problem here. I have read this article http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx, but still have no idea.... :3
If you call the ReportProgress method too rapidly, it's possible that the UI thread will not have a chance to process the "progress" and update appropriately before the BackgroundWorker is hitting it again.
private void doWork(object sender, DoWorkEventArgs e)
{
var wrk = sender as BackgroundWorker;
// obviously you wouldn't really do this :)
while(true)
wrk.ReportProgress(0);
}
To see the effect you're looking expecting, you could set an artificial "pause" in your DoWork event, in order to give the UI time to update appropriately:
private void doWork(object sender, DoWorkEventArgs e)
{
var wrk = sender as BackgroundWorker;
var p = 0;
while(true)
{
wrk.ReportProgress(p++);
Thread.Sleep(100);
}
}
As for your situation, if the code is executing that quickly, you may not actually need to be executing it in a separate thread.
Alternatively, you could update your UI to say "Please wait. Loading...", then do everything you need to do in the BackgroundWorker, and just return the final result back to the UI at the end.
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();
private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
n = Convert.ToInt16(DF_Engine);
}
}
}
Form2.Engine1, Form2.Engine2....... are the values come from Form2 when the corresponding checkbox is selected. these would be 1, 2 , 3 so on....
My code send 01 when checkbox1 is selected but it send 01,02 without any delay when checkbox1 and checkbox2 are selected in Form2.
I need delay when asking 01 and 02 and so on as per my interest.
how could i do it convineintly
and when i use Thread.Sleep(500), the application gets slow.
need guidance.
Do not use Thread.Sleep on UI thread. It will definitely slow down your application. Rather start a new thread, pass data to that thread and let that thread send input to your application with delay.
System.Threading.Thread thread = new System.Threading.Thread((inputList) =>
{
foreach (var input in inputList as IEnumerable<int>)
{
//Send input
System.Threading.Thread.Sleep(500);
}
});
thread.Start();
Where inputList is an array of data(1,2,etc depending upon your checkbox selected).
You can consider using a separate thread of just a BackgroundWorker control. This way, you could use Thread.Sleep() method and it should not make the application's GUI unresponsive. You can find some more information about it on msdn: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx.
Just add a BackgroundWorker control to your form and use this code to handle appropriate events:
private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
//disable timer1, so it wont tick again until the current work is finished
timer1.Stop();
//start processing asynchronously, so GUI is still responsive
sampleBackgroundWorker.RunWorkerAsync(DF_Engines);
}
//this method should be attached to DoWork event of the BackgroundWorker
private void sampleBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
string[] DF_Engines = (e.Argument as string[]);
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
//n gets overwritten in each iteration, is this line required?
n = Convert.ToInt16(DF_Engine);
Thread.Sleep(500);
}
}
//you can also pass a result from this method back to the GUI thread like this
//e.Result = "job done";
//this can be read later in RunWorkerCompleted method of the BackgroundWorker
}
//this method should be attached to RunWorkerCompleted event of the BackgroundWorker
private void sampleBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//start timer1, so it can invoke the worker again
timer1.Start();
}
If you need more help with this, let me know.
I have a form with 2 comboboxes on it. And I want to fill combobox2.DataSource based on combobox1.Text and combobox2.Text (I assume that the user has completed input in combobox1 and is in the middle of inputting in combobox2). So I have an event handler for combobox2 like this:
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
As far as building DataSource is time-consuming process (it creates a request to database and executes it) I decided that it's better to perform it in another process using BackgroundWorker. So there's a scenario when cmbDataSourceExtractor hasn't completed its work and the user types one more symbol. In this case I get an exception on this line
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); about that BackgroundWorker is busy and cannot perform several actions in the same time.
How to get rid of this exception?
CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being run in the background must periodically check this property and handle the cancellation itself.
The tricky part is that your DoWork delegate is probably blocking, meaning that the work you do on your DataSource must complete before you can do anything else (like check for CancellationPending). You may need to move your actual work to yet another async delegate (or maybe better yet, submit the work to the ThreadPool), and have your main worker thread poll until this inner worker thread triggers a wait state, OR it detects CancellationPending.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
If you add a loop between the CancelAsync() and the RunWorkerAsync() like so it will solve your problem
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
The while loop with the call to Application.DoEvents() will hault the execution of your new worker thread until the current one has properly cancelled, keep in mind you still need to handle the cancellation of your worker thread. With something like:
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
The Application.DoEvents() in the first code snippet will continue to process your GUI threads message queue so the even to cancel and update the cmbDataSourceExtractor.IsBusy property will still be processed (if you simply added a continue instead of Application.DoEvents() the loop would lock the GUI thread into a busy state and would not process the event to update the cmbDataSourceExtractor.IsBusy)
You will have to use a flag shared between the main thread and the BackgroundWorker, such as BackgroundWorker.CancellationPending. When you want the BackgroundWorker to exit, just set the flag using BackgroundWorker.CancelAsync().
MSDN has a sample: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx
MY example . DoWork is below:
DoLengthyWork();
//this is never executed
if(bgWorker.CancellationPending)
{
MessageBox.Show("Up to here? ...");
e.Cancel = true;
}
inside DoLenghtyWork :
public void DoLenghtyWork()
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
inside OtherStuff() :
public void OtherStuff()
{
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
What you want to do is modify both DoLenghtyWork and OtherStuff() so that they become:
public void DoLenghtyWork()
{
if(!bgWorker.CancellationPending)
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
public void OtherStuff()
{
if(!bgWorker.CancellationPending)
{
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
The problem is caused by the fact that cmbDataSourceExtractor.CancelAsync() is an asynchronous method, the Cancel operation has not yet completed when cmdDataSourceExtractor.RunWorkerAsync(...) exitst. You should wait for cmdDataSourceExtractor to complete before calling RunWorkerAsync again. How to do this is explained in this SO question.
My answer is a bit different because I've tried these methods but they didn't work. My code uses an extra class that checks for a Boolean flag in a public static class as the database values are read or where I prefer it just before an object is added to a List object or something as such. See the change in the code below. I added the ThreadWatcher.StopThread property. for this explation I'm nog going to reinstate the current thread because it's not your issue but that's as easy as setting the property to false before accessing the next thread...
private void combobox2_TextChanged(object sender, EventArgs e)
{
//Stop the thread here with this
ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
all fine
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
Now add the following class
public static class ThreadWatcher
{
public static bool StopThread { get; set; }
}
and in your class where you read the database
List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
break;
list.Add(something);
...
don't forget to use a finally block to properly close your database connection etc. Hope this helps! Please mark me up if you find it helpful.
In my case, I had to pool database for payment confirmation to come in and then update WPF UI.
Mechanism that spins up all the processes:
public void Execute(object parameter)
{
try
{
var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
Process.Start(new ProcessStartInfo(url));
ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
}
catch (Exception e)
{
ViewModel.Log.Error("Failed to navigate to payments", e);
MessageBox.Show("Failed to navigate to payments");
}
}
Mechanism that does checking for completion:
private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(30000);
while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
{
Thread.Sleep(5000);
}
//Plug in pooling mechanism
this.AuthCode = GetAuthToken();
}
Mechanism that cancels if window gets closed:
private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
{
var context = DataContext as PaymentViewModel;
if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
context.UpdateUiWhenDoneWithPayment.CancelAsync();
}
I agree with guys. But sometimes you have to add more things.
IE
1) Add this worker.WorkerSupportsCancellation = true;
2) Add to you class some method to do the following things
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
So before close your application your have to call this method.
3) Probably you can Dispose, null all variables and timers which are inside of the BackgroundWorker.