Accessing UI from outside thread in Winforms - c#

In WPF, one could use something like:
Application.Current.Dispatcher.BeginInvoke(new Action(() => Form1.grid.Items.Refresh()));
to access UI functions outside of the main thread. In Winforms however, the same functionality isn't there. What would be the easiest way to access a BindingList that exists inside of my Form1 class from my "working" thread? Currently I getting the following error when trying to access "Form1.record_list":
System.InvalidOperationException: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
edit: I appreciate the help so far, but I'm lost on "this.Invoke". My method in the separate thread has no "Invoke".
Here's an example of my code so far.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
Form1.record_list.Add(record); //This is where I assume the problem spawns
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public partial class Form1 : Form
{
public static BindingList<Record> record_list = new BindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
Thread thread = new Thread(SCMClient.connect);
thread.IsBackground = true;
thread.Start();
FillData();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
foreach (DataGridViewRow row in dataGridView.Rows)
{
for (var i = 0; i < row.Cells.Count; i++)
{
Console.WriteLine(row.Cells[i].Value);
if (row.Cells[i].Value as string == "OK")
{
row.Cells[i].Style.BackColor = Color.Red;
Console.WriteLine("IF WAS TRUE");
}
}
}
}
I think the specific problem here is when I add Records to the Forms1.record_list. I'm not sure how to add items to that list without causing the Cross-thread error...

You MUST access your UI from your UI Thread only. But you can use the Control.Invoke method - a Form IS a Control - to ensure your code is run from the UI Thread.
Also, you have a static BindingList, but I assume you want to display the contents of that list in an specific form. You should make the BindingList an instance member instead... or get a reference to a valid Form. The Control.Invoke method is not static.
There are several ways to do so. I would do it like so:
First, create a method in your Form class that adds the record to the list.
public void AddRecord(Record r) {
if(this.InvokeRequired) {
this.Invoke(new MethodInvoker(() => this.AddRecord(r)));
} else {
this.record_list.Add(r);
}
}
Second, you need to have a reference to the form (in the next step, that is the theForm variable).
Then, in your listener method, invoke AddRecord method instead of adding the record in your BindingList directly.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
theForm.AddRecord(record); // You need a Form instance.
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

The below will work out in winforms. Have tried once. The below code helps to update the label in UI thread from another thread.
string _string = "Call from another thread";
this.Invoke((MethodInvoker)delegate {
label1.Text = _string;
});

You're looking for SynchronizationContext.Current, which works with both WinForms and WPF.
Note that you'll need to grab its value from the UI thread, since it's per-thread.

Related

When I use cross thread then my application doesn't respond until background operations complete

I'm working on windows from application in .net framework 2.0.
There is some operations run in background like database backup, progress bar and label text update etc.
But When I use cross thread then my application doesn't respond(busy icon) until background operations complete
This is example code
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(UpdateInfo));
t.Start();
}
private void UpdateInfo()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(UpdateInfo));
}
else
{
// send query to database here for taking backup that could take time
// update progress bar
//I'm also using sqlconnection InfoMessage here
label1.Text = "Text upading......
}
}
private void OnInfoMessage(sender As Object, e As SqlInfoMessageEventArgs)
{
}
Scenario:
Scenario is user could cancel operation but it can't due to application not responding
================Update Code==========================================
My Code is like
private void btnBackup_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(MyThreadFunc));
t.Start();
}
public void MyThreadFunc()
{
if (this.InvokeRequired) {
this.Invoke(new MethodInvoker(Backup));
} else {
Backup();
}
}
public void Backup()
{
string databaseName = cbDatabase.Text;// getting the name of database for backup
SaveFileDialog1.ShowDialog(); // dialog will open
string backupFileName = SaveFileDialog1.FileName; // getting location of backup
//============ database query==================
SqlConnection con = new SqlConnection(conString);
con.FireInfoMessageEventOnUserErrors = true;
con.InfoMessage += OnInfoMessage;
con.Open();
query = string.Format("backup database {0} to disk = {1}", databaseName,backupFileName);
using (cmd == new SqlCommand(query, con)) {
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
con.Close();
con.InfoMessage -= OnInfoMessage;
con.FireInfoMessageEventOnUserErrors = false;
//============ Database operation end==================
}
private void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
{
lblStatusMsg.Text = e.Message; // mostly messages are like. 1 percent complete, 5 percent complete, 11 percent complete
foreach (SqlError info in e.Errors) {
if (info.Class > 10) {
// errror logging
} else {
Regex reger = new Regex("\\d+");
Match regerMatch = reger.Match(e.Message);
if (ProgressBar1.Value == 100) {
} else {
ProgressBar1.Value = regerMatch.Value;
}
}
}
}
Not responding issue until database operation completes
The purpose of the Invoke call is to have code run on the main thread. Your code is therefore creating a thread whose entire purpose is to force the main thread to run all the code.
Let's assume that you want to run a thread that, 10 seconds after it starts, updates a label's text to indicate completion. You still need to Invoke the label update, but that's the only thing that should be in the invoke.
In that case your thread function should look something like this:
private void MyThreadFunc()
{
// do something here
Thread.Sleep(10000);
// update the label:
if (label1.InvokeRequired)
Invoke(UpdateLabel);
else
UpdateLabel();
}
private void UpdateLabel()
{
label1.Text = "Something was finished.";
}
In other words, you need to separate out those things that have to run on the main thread (like anything that updates controls on your form) and Invoke only those bits. The rest of it should happen outside of the Invoke.
I guess I didn't make it clear.
The Invoke method is used to execute code in the context of the thread that owns the handle of the control or form that you're invoking on. You can use this to interact with controls on the UI, but you should only use it for that purpose. If you put all of the thread's close in an Invoke call then all of the thread's code will run in the UI thread, which makes it completely pointless to have a separate thread.
If you want to stop your application's UI from pausing while things happen - which is, after all, one of the main reasons to use a thread - then you should use the Invoke method only when absolutely necessary, and then only for very small sections of code. Call Invoke to update a control's parameters, interact with the non-threadsafe properties of the form, etc. You can use dialog boxes and so on directly from your other thread, although some prefer to use Invoke for those as well.
And if you're doing multiple invokes then you probably should write some helper methods to wrap the Invoke to clean things up. Something like:
public void Invoker(Action action)
{
if (InvokeRequired)
Invoke(action);
else
action();
}
public T Invoker<T>(Func<T> func)
{
if (InvokeRequired)
return (T)Invoke(func);
else
return func();
}
Now you can write your thread code with minimal impact like this:
public void ThreadFunc()
{
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = "Started");
for (int i = 1; i < 10; i++)
{
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = string.Format("Iteration {0}", i));
}
System.Threading.Thread.Sleep(1000);
Invoker(() => this.label1.Text = "Completed");
}
Or if you don't like lambda functions (for some reason) you can use methods like this:
public void Invoker<T>(Action<T> action, T p)
{
if (InvokeRequired)
Invoke(action, p);
else
action(p);
}
private void SetLabel(string value)
{
label1.Text = value;
}
And then in your code:
Invoker(SetLabel, "new text value");
The important part is to keep the code you're invoking be tiny or you'll end up blocking your main thread.

For-loop multi-threading passes upper bound

We are learning multi-threadding today in class and we came across a very curious error. When doing a for loop in our new thread the upper bound of the for loop keeps getting passed. The thread is being killed but then another value will appear and end another thread.
For the purpose of debugging the error I changed the upper bound to 90 to avoid the OutOfRange Exception on the progressbar.
While outputting the counter to the progressing bar and updating the progress bar I got this in my output window.
If i commented out the updating on the progress bar (pbLoad.Value = i;) I got this in my output window
I have tried changing the loop to i<101 and also tried moving where the i++ was but it made no difference
EDIT: This is coming from the BeginInvoke. When i switched it to Invoke it worked but then I will get a deadlock when trying to use the cancel button.
Here is the code:
public partial class Form1 : Form
{
Thread backgroundThread;
bool stopExecution = false;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
stopExecution = false;
btnStart.Enabled = false;
backgroundThread = new Thread(DoDomethingThatTakesAWhile);
backgroundThread.Start();
}
private void DoDomethingThatTakesAWhile()
{
for (int i = 0; i <= 100; i++)
{
if (!stopExecution)
{
Thread.Sleep(100);
if (pbLoad.InvokeRequired)
{
MethodInvoker myMethod
= new MethodInvoker(
delegate
{
if (!stopExecution)
{
pbLoad.Value = i;
Debug.WriteLine(i); //i to output window
}
});
pbLoad.BeginInvoke(myMethod);
}
else
{
pbLoad.Value = i;
}
}
else
{
break;
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
//backgroundThread.Abort();
stopExecution = true;
backgroundThread.Join();
pbLoad.Value = 0;
btnStart.Enabled = true;
}
}
When you call MethodInvoke it will not occurs at that moment, but some time later.
In your scenario you have a chance of following to occurs:
invoked code is finally executed;
the loop is already finished (and i become 101)
you are accessing i directly and you read 101.
And to fix it you can make a copy of i (by passing it as a parameter to invoked method):
pbLoad.BeginInvoke(new Action<int>(a =>
{
if (!stopExecution)
{
pbLoad.Value = a;
Debug.WriteLine(a); //a to output window
}
}), new object[] { i });
P.S: you don't need to check for InvokeRequired, unless you plan to call DoDomethingThatTakesAWhile method directly, which I assume is not the case.
You're using BeginInvoke which explicitly opens the possibility for races. I recommend synchronous invoking.
Furthermore, you are capturing i, not its value. This is racy and only works by accident because you're sleeping.
Either of the changes will fix the problem. Do both of them.
If you can, abolish this low-level use of synchronization and use async/await.

Form freezes when opened for second time

I am developing a Windows Forms application that access a WCF service. I ran into a great problem that I can't predict the reason of it. Even the Visual Studio debugger not showing any exception in the Output view. The scenario is like this, I have a custom user control that has a linkLabel on it. Whenever the link label is clicked, a form is opened and a class object is passed to it. The class definition of this object resides on WCF service on a remote server. Now the problem is that when I click the linkLabel, the form opens perfectly loading each of its component according to the class object passed to it. But when I close this form and click that linkLabel again, the form opens but immediately freezes after loading some elements. I tried many code variations. Edited many part of code that I think can affect. But none of them showed the difference. Since, I don't know where actually is the code has error, I am posting the linkLabel click code and functions that are called after it is clicked.
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Enabled = false;
string temp = Title.Text;
Title.Text = "Opening...";
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(openTopic));
t.Start();
Title.Text = temp;
Enabled = true;
}
void createTopicWindow()
{
TopicViewer t = new TopicViewer(t);
Invoke(new Action(() => t.Show()));
}
private void openTopic()
{
Invoke(new Action(() => createTopicWindow()));
}
The above is the edited code, since I was getting Cross thread exception before.
Following is the code of constructor of the form that is called when clicked the linkLabel:
try
{
InitializeComponent();
this.t = topic;
if (IsHandleCreated == false)
CreateHandle();
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(loadTopic));
th.Start();
Common.openedTopics.Add(this);
AddComment addComment1 = new AddComment();
addComment1.Topic = t;
addComment1.Dock = DockStyle.Fill;
panel5.Controls.Add(addComment1);
backgroundWorker1.RunWorkerAsync();
}
catch (Exception)
{ }
void loadTopic()
{
Invoke(new Action(()=>tHead = new TopicHeader()));
Global.SetControlPropertyThreadSafe(tHead,"Topic", t);
Global.SetControlPropertyThreadSafe(tHead,"Dock", DockStyle.Fill);
Invoke(new Action(()=>panel1.Controls.Add(tHead)));
Global.SetControlPropertyThreadSafe(this,"Text", t.Title + " - Topic Viewer");
if (t.Description.Trim().Length > 0)
{
Global.SetControlPropertyThreadSafe(webBrowser1, "DocumentText", t.Description);
}
else
{
Invoke(new Action(() => tabControl1.TabPages[0].Dispose()));
}
Global.SetControlPropertyThreadSafe(tabPage2, "Text", "Comments (" + client.getComCount(t.TopicID) + ") ");
}
TopicHeader is another small user control.
Please anyone tell me the solution to this?
If you are using .Net 4.5, then using async/await would be easiest solution. That way, you don't need any Invokes
async private void Form1_Load(object sender, EventArgs e)
{
string s = await Task<string>.Factory.StartNew(LongRunningTask,
TaskCreationOptions.LongRunning);
this.Text = s;
}
string LongRunningTask()
{
Thread.Sleep(10000);
return "------";
}
I can't give a direct answer to you question, but this may give a hold on.
public void Form_Load()
{
// do some stuff on the gui-thread
// i need to do something that takes a long time:
ThreadPool.QueueUserWorkItem((state) =>
{
// i'll execute it on the ThreadPool
// Long running code....
// update results in mainform on gui thread.
Invoke(new Action( delegate
{
// because the invoke will execute this on the gui-thread, you'll able to update controls.
// update my gui controls.
DataGrid.Source = myReceiveDataThing;
}));
}
}
You might expand the code, to check if the form is still valid.

Threading, let one thread know the others progress

Ok, well I have been at it for a while now and I decided to just use threads. I am making a syntax highlighter but I keep getting terrible performance with the file sizes that it will usually be used for. So I made two forms, the first shows the file in plain text and has a button that says "openincolor" when you click that I start a new thread as such
private void button1_Click(object sender, EventArgs e)
{
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
public class ColoringThread
{
string text;
public ColoringThread(string initText)
{
text = initText;
}
public void OpenColorWindow()
{
Form2 form2 = new Form2(text);
form2.ShowDialog();
}
};
I want this form to send back a message each time it has complete say x lines of coloring. Then I will take that and figure out the progress and display it to the user.
How might I go about sending a message, or event(...? can I do that) to my first form to let it know of the others progress?
One very simple way to do this is with BackgroundWorker. It already provides an event to report progress.
How about something like this? This adds an event to the ColoringThread class which is subscribed to by the calling class.
private void button1_Click(object sender, EventArgs e) {
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
colorer.HighlightProgressChanged += UpdateProgress;
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
private void UpdateProgress(int linesComplete) {
// update progress bar here
}
public class ColoringThread
{
string text;
public delegate void HighlightEventHandler(int linesComplete);
public event HighlightEventHandler HighlightProgressChanged;
public ColoringThread(string initText) {
text = initText;
}
public void OpenColorWindow() {
Form2 form2 = new Form2(text);
form2.ShowDialog();
int linesColored = 0;
foreach (String line in text.Split(Environment.NewLine)) {
// colorize line here
// raise event
if (HighlightProgressChanged != null)
HighlightProgressChanged(++linesColored);
}
}
};
You can pass an object as argument to the Thread.Start and share your data between the current thread and the initiating thread.
Here is a good example:
How to share data between different threads In C# using AOP?
Or you can use BackgroundWorker which has ReportProgress
What you need is System.Windows.Threading.Dispatcher's BeginInvoke method. You can't directly modify a WPF object from your background thread, however you can dispatch a delegate to do that.
In your derived Window class object you have the Property Dispatcher, so you use it as follows:
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(status) => { StatusTextBox.Text = status },
thestatus
);
I'm sorry that I can't test that currently and I don't have the project here, where I did that. But I'm sure it will work, good luck ;)
Update: Oops, you're using Form's... I've written about WPF, sorry.

How to stop BackgroundWorker correctly

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.

Categories