Proper way to cancel BackgroundWorker - c#

Its my first time working on a wpf. An existing system has to process an excel file, now the requirement is, the excel file must have five comlumns. Here is the code that has to do the processing
void InsertIDsNamesAndAddWorker_DoWork(object sender, DoWorkEventArgs e)
{
// read the excel file here
int columns = xlWorkSheet.UsedRange.Columns.Count;
if (columns == 5)
{
//do your normal processing
}
else
{
//if requirements are not met then display error message
System.Windows.MessageBox.Show("There must be five columns in this
file", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Now this code does get into the else part, it however continues to other part which then displays an error message that says "done processing".
Here is the current code of the method that does the confirmation message
void InsertIDsNamesAndAddWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBarValue = 100;
StatusLable = "Done Processing.";
if (System.Windows.MessageBox.Show("Done Processing.", "Status", MessageBoxButton.OK, MessageBoxImage.Information) == MessageBoxResult.OK)
{
StatusLable = string.Empty;
ProgressBarValue = 0;
}
}
Now with me being new to the wpf technology, I realized that the hard-coded value of Statuslable is the one causing issues, so I went to set the ProgressBarValue to 100 if the the requirements were met and processing is done.
I also set the ProgressBarValue to zero if the colums was not equal to 5.
Here is the new code
void InsertIDsNamesAndAddWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
int count = ProgressBarValue;
if (count != 100)
{
StatusLable = string.Empty;
ProgressBarValue = 0;
}
else
{
//ProgressBarValue = 100;
StatusLable = "Done Processing.";
if (System.Windows.MessageBox.Show("Done Processing.", "Status", MessageBoxButton.OK, MessageBoxImage.Information) == MessageBoxResult.OK)
{
StatusLable = string.Empty;
ProgressBarValue = 0;
}
}
}
My main question is, is this the right way though? is the any other way I can cancel the work if requirements are not met?

Use the Result prorperty of the DoWorkEventArgs instance you get passed in DoWork:
void InsertIDsNamesAndAddWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (columns == 5)
{
//do your normal processing
e.Result = true; // we're OK
}
else
{
//if requirements are not met then display error message
System.Windows.MessageBox.Show("There must be five columns in this
file", MessageBoxButton.OK, MessageBoxImage.Error);
e.Result = false; // something wrong
}
}
and then in RunWorkerCompleted check the Result value and handle accordingly.
void InsertIDsNamesAndAddWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// check if we're not cancelled or error-ed before checking the Result
result = (!e.Cancelled && e.Error == null)? (bool) e.Result: false; // what is the outcome
ProgressBarValue = 100;
if (result) {
StatusLable = "Done Processing.";
if (System.Windows.MessageBox.Show("Done Processing.", "Status", MessageBoxButton.OK, MessageBoxImage.Information) == MessageBoxResult.OK)
{
StatusLable = string.Empty;
ProgressBarValue = 0;
}
}
else
{
StatusLable = "Error in Excel sheet";
}
}
Notice that Result is of type object. You can put any instance of a type into it, even your own class, which might be needed if you want to return more fine grained details about what went wrong.

The BackgroundWorker has a property called WorkerSupportsCancellation. If this is set to true, you've got another option to cancel the execution.
Whenever something wrong happens, you can call backgroundWorker.CancelAsync(), which will set a boolean to true (which is the CancellationPending property in the BackgroundWorker object).
You can then check, during execution, if the CancellationPending is true. If so, the worker should stop.
If the worker stops, it'll launch the RunWorkerCompleted event, which will end up in the handler for the method (if any is added).
This way of cancelling can be checked at all instructions, or at the start of a for loop (as in: for (int i = 0; (i < x) && worker.CancellationPending; i++) ;)
Hope this helps!

Related

C# console Abort multiple threads

I work on console version of link tester.
I start the function on multiple threads but I can't cancel them by key pressing.
Have an idea how I can do that ?
try
{
Thread[] tr = new Thread[Variables.Threads];
int i = 0;
while (i < Variables.Threads && Variables.running)
{
tr[i] = new Thread(new ThreadStart(Program.Runner));
i++;
}
//Start each thread
foreach (Thread x in tr)
{
x.Start();
}
//Console.ReadKey();
Task.Factory.StartNew(() =>
{
while (Colorful.Console.ReadKey().Key != ConsoleKey.Escape);
Variables.running = false;
foreach (Thread x in tr)
{
x.Abort();
}
Program.Menu();
});
}
catch (Exception)
{
}
EDIT : When my threads are a near of end, all of my app don't move after
The console is a not a good environment to start learning multitasking, much less multithreading. My advise to learning Multitasking - and especially Multithreading - is the BackgroundWorker in a WindowsForms applciation. The event queue does the main thing of keeping your application alive, without blocking I/O. And while the BackgroundWorker is horribly dated and should be removed from production code, it is pretty good training wheels.
Also your current code is swallowing exceptions, including fatal ones. This is a cardinal sin of exception handling. Do not do that.
At the end of the day, you can only do cancelation checking and progress reporting between distinct lines of code. In this BGW example, I had the advantage that I had to write all the loops anyway - so deep cancelation checking and progress reporting was in the cards. But if you use "somebody elses code", chances are high you have to wait for one function call to return before you can report and check.
#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)
{
if (!bgwPrim.IsBusy)
{
//Prepare ProgressBar and Textbox
int temp = (int)nudPrim.Value;
pgbPrim.Maximum = temp;
tbPrim.Text = "";
//Start processing
bgwPrim.RunWorkerAsync(temp);
}
}
private void btnPrimCancel_Click(object sender, EventArgs e)
{
if (bgwPrim.IsBusy)
{
bgwPrim.CancelAsync();
}
}
private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)
{
int highestToCheck = (int)e.Argument;
//Get a reference to the BackgroundWorker running this code
//for Progress Updates and Cancelation checking
BackgroundWorker thisWorker = (BackgroundWorker)sender;
//Create the list that stores the results and is returned by DoWork
List<int> Primes = new List<int>();
//Check all uneven numbers between 1 and whatever the user choose as upper limit
for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
{
//Report progress
thisWorker.ReportProgress(PrimeCandidate);
bool isNoPrime = false;
//Check if the Cancelation was requested during the last loop
if (thisWorker.CancellationPending)
{
//Tell the Backgroundworker you are canceling and exit the for-loop
e.Cancel = true;
break;
}
//Determin if this is a Prime Number
for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
{
if (PrimeCandidate % j == 0)
isNoPrime = true;
}
if (!isNoPrime)
Primes.Add(PrimeCandidate);
}
//Tell the progress bar you are finished
thisWorker.ReportProgress(highestToCheck);
//Save Return Value
e.Result = Primes.ToArray();
}
private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbPrim.Value = e.ProgressPercentage;
}
private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pgbPrim.Value = pgbPrim.Maximum;
this.Refresh();
if (!e.Cancelled && e.Error == null)
{
//Show the Result
int[] Primes = (int[])e.Result;
StringBuilder sbOutput = new StringBuilder();
foreach (int Prim in Primes)
{
sbOutput.Append(Prim.ToString() + Environment.NewLine);
}
tbPrim.Text = sbOutput.ToString();
}
else
{
tbPrim.Text = "Operation canceled by user or Exception";
}
}
#endregion
However in your case, Multithreading seems unesseary. Multitasking without Threads would propably be better. Threads only help if you got a CPU bound task. And "checking links" sounds like a Network bound task. Threads have some extra headaches that you are better off avoiding.

Try catch - good practice? User input at run time

This question builds on the question I have asked 4 days ago complex if statement in an text changed event. I tried a lot so that the problem System.ArgumentOutOfRangeException does not occur. The problem arises because the if-statement is in a text changed event and checked 8 characters, that arise at runtime. So, not all characters are immediately available but are generated dynamically by the input of the user. This is where the problem starts, every time the text in the textbox changes, the event is fired and at the same time, the program immediately expects 8 characters and logically gives the error message System.ArgumentOutOfRangeException. To deal with this problem, I have put the hole if-statement in a try-catch block. Now it works, but is this a good practice? Are there other / better solutions? Here's an excerpt from my code:
private void txtBoxEingabe_TextChanged(object sender, EventArgs e)
{
axAcroPDF1.LoadFile("DONTEXISTS.pdf");
radioButton1.Visible = false;
radioButton2.Visible = false;
string text = txtBoxEingabe.Text.Substring(0, txtBoxEingabe.TextLength);
try
{
if (text.Substring(0, 3) == "SEH" && text.Substring(3, 1) == "M" && Convert.ToInt32(text.Substring(4, 4)) <= 2999 && (text.Substring(8, 1) == "H" || text.Substring(8, 1) == "R"))
{
radioButton1.Visible = true;
radioButton2.Visible = true;
radioButton1.Text = "1. Document";
radioButton2.Text = "2. Document";
this.radioButton1.CheckedChanged += RadioBtnChangedDC1;
this.radioButton2.CheckedChanged += RadioBtnChangedDC1;
}
}
catch
{
}
private void RadioBtnChangedDC1(object sender, EventArgs e)
{
if (radioButton1.Checked == true)
{
axAcroPDF1.LoadFile("C:\Doc1.pdf");
axAcroPDF1.gotoFirstPage();
Screensize = 100;
axAcroPDF1.setZoom(Screensize);
axAcroPDF1.setShowScrollbars(true);
axAcroPDF1.setShowToolbar(false);
}
else if (radioButton2.Checked == true)
{
axAcroPDF1.LoadFile("C:\Doc2.pdf");
axAcroPDF1.gotoFirstPage();
Screensize = 100;
axAcroPDF1.setZoom(Screensize);
axAcroPDF1.setShowScrollbars(true);
axAcroPDF1.setShowToolbar(false);
}
}
This program should be a viewer that shows hundreds of several documents.
This would be bad practice as you are not checking for the error and are instead relying on an exception.
Do this instead, and you wan't need a try/catch there.
if (txtBoxEingabe.Text.Length < 8)
return;

Remove CheckListBox CheckedItems

I have a timer event that does several things. One item I am trying to get it to do is to programmatically remove the CheckListBox items that are checked once the timer hits the completed action I am performing.
This is the code for the timer and what I have tried to do.
private void timer1_Tick(object sender, EventArgs e)
{
string s;
if (DbFirmwareUpdateComplete.WaitOne(1))
{
DbFirmwareUpdateComplete.Reset();
mnuLoadKeyFile.Enabled = true;
}
if (DbUpdateComplete.WaitOne(1))
{
DbUpdateComplete.Reset();
mnuLoadKeyFile.Enabled = true;
btnLoad.Enabled = true;
}
if (CacheComplete.WaitOne(1))
{
CacheComplete.Reset();
btnLoad.Enabled = true;
}
if (UpdateRunning)
{
bool UpdateDone = true;
int StillActive = 0;
// loop through all active jobs to check if all have completed
foreach (clsCnaPair cna in ActiveJobs)
{
if (cna.Job.JobComplete == false)
{
UpdateDone = false;
StillActive++;
}
else
{
if (cna.Job.UpdateSuccess)
{
// Update color of CLB.Items.Selected if success.
int count = CLB.Items.Count;
for (int index = count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
}
}
}
else
{
// Update color of CLB.Items.Selected if failed.
}
}
}
if (UpdateDone)
{
UpdateRunning = false;
log("All Update jobs have finished.");
}
if (ckTop.Checked == true)
{
ckTop.Checked = false;
}
else
{
ckTop.Checked = false;
}
When I run the program and it hits this piece;
if (cna.Job.UpdateSuccess)
{
// Update color of CLB.Items.Selected if success.
int count = CLB.Items.Count;
for (int index = count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
}
}
}
I get an error:
System.ArgumentOutOfRangeException: InvalidArgument=Value of '-1' is not valid for 'index'.
Parameter name: index
The Error occurs after this piece of code;
private void CLB_SelectedIndexChanged(object sender, EventArgs e)
{
// One of the CNA IPs was selected. sender is the CheckedListBox.
// Here we want to display its fingerprint in the text box, or if the push is running, the status.
// get the CnaPair class represented by this IP:
clsCnaPair CnaPair = (clsCnaPair)CLB.Items[CLB.SelectedIndex];
// Display the corresponding fingerprint string in the editBox:
if (CnaPair.Job != null) txtStatus.Text = CnaPair.Job.GetStatus();
else txtStatus.Text = CnaPair.GetInfo();
}
Or more specifically at the line:
clsCnaPair CnaPar = (clsCnaPair)CLB.Items[CLB.SelectedIndex];
What am I missing? Searching google, shows the way I am doing the remove is consistent with the examples found there.
Thanks,
It's dangerous to modify the contents of the ChecklistBox inside a loop when the loop conditions depend on the contents of the ChecklistBox. Once you call RemoveAt(), the CheckedItems list and the CLB.Items.Count has changed and you will have a problem. In this case, the loop fired the SelectedIndexChanged() event with an invalid Index (-1).
Better to do this in a do-while loop:
bool done;
do
{
done = true;
for (int index = CLB.Items.Count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
done = false;
break;
}
}
}while(!done);
This way, every time an item is removed, you break out and start the loop all over again.
After some experimentation, I commented out the CLB_SelecteIndexChanged code and it now completes with the original code.
That leaves one issue. What is the work around with the CLB_SelectedIndexChanged code left in. I will work on that one more and see f I can figure it out with what you guys have provided.
Thanks to both m.rogalski and mcNets.

C# BackgroundWorker Completed Called Way Before Completion

I have been trying to work out why my background worker is 'finishing' its work when there is still a lot for it to do. I am actually in the process of refactoring the code for this app, so it did work in the past, but now I am unable to figure out what has gone wrong.
Specifically, the app should open Outlook and then perform a few checks. However, the background worker exits straight after Outlook is opened for no apparent reason (as you will se below there is still plenty of processing to be done).
This appears to be happening early on in the Start() method, directly after calling Process.Start() on Outlook.exe.
The code runs in this order:
calling the background worker - this was the user's choice from a radio set
....
else if (radioButton5.Checked == true)
{
textBox1.Text = "Please wait while your session restarts";
pageControl1.SelectedIndex = 10;
backgroundReset.RunWorkerAsync();
}
The do-work method
public void backgroundReset_DoWork(object sender, DoWorkEventArgs e)
{
backgroundReset.WorkerSupportsCancellation = true;
Session.Reset();
}
the reset session method starts by killing the current session ...
public static void Reset()
{
KillSession();
System.Threading.Thread.Sleep(5000);
Start();
// THE BACKGROUNDWORKER EXITS BEFORE HERE!
if (IsLoggedIn() == false)
{
return;
}
else
{
// Make sure Lync is open before finishing the process ...
var j = 0;
GetSession(Init.servers);
j = 0;
var checker = false;
checker = ProcessHandler.CheckRunning("lync.exe");
while (checker == false)
{
if (j == 100)
{
break;
}
Thread.Sleep(500);
checker = ProcessHandler.CheckRunning("lync.exe");
j++;
}
}
}
As you can see from the comment, the backgroundworder is calling RunWorkerCompleted way before the Reset() method has finished executing.
Below are the other methods called (kill, logoff, start):
KillSession logs the session of and then makes sure it is logged off
private static void KillSession()
{
if (sessionId != null)
{
LogOff();
for (int i = 0; i < 150; i++)
{
if (IsLoggedIn() == true)
{
Thread.Sleep(500);
}
else
{
break;
}
}
}
}
LogOff sends a Cmd command to log off the current session
public static void LogOff()
{
string strCmdIn = "/C LOGOFF " + sessionId + " /SERVER:" + serverName;
Cmd.Exec(strCmdIn);
}
Start() Simply opens Outlook, causing a Citrix session to also start. The app is definitely launching Outlook, but after that it doesn't reach either of the for statements - the BackgroundWorker just exits.
public static void Start()
{
Process.Start(appDataCitrix + "Outlook.exe");
for (int i = 0; i < 15; i++)
{
if (IsLoggedIn2() == false)
{
Thread.Sleep(1000);
}
else
{
break;
}
}
if (IsLoggedIn2() == false)
{
Process.Start(appDataCitrix + "Outlook.exe");
for (int i = 0; i < 10; i++)
{
if (IsLoggedIn2() == false)
{
Thread.Sleep(1000);
}
else
{
break;
}
}
}
}
Does anyone have any idea what is going on here? It is driving me crazy!
Many thanks
Update
The RunWorkerCompleted Method:
As far as my understanding goes, this has no baring on when the process will finish.
public void backgroundReset_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Session.IsLoggedIn())
{
btnFailFinish.Visible = true;
label10.Text = Session.serverName;
pageControl1.SelectedIndex = 3;
}
else
{
pageControl1.SelectedIndex = 10;
pictureBox2.Visible = false;
textBox1.Text = "Double-click Outlook on your desktop to launch a new session.";
textBox15.Text = "Once you have done this please click Finish.";
pictureBox9.Visible = true;
}
}
This is probably because of an exception being thrown from within the start method.
You may either add a try / catch block all around this method and handle the error from within the catch, or check in the RunWorkerCompleted method if an exception occurred :
private void RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// handle your exception here
}
}

How to update button text after event

I'm trying to let a program post a bunch of text. The user enters text, the amount of messages and how fast these must be delivered. While the program is busy, the button text needs to be "Stop" instead of "Start". When you press the button to force it to stop after you've initially launched it, the text changes back to "Start", but this doesn't happen when the program stops after the given amount of messages are delivered, even though the code is in place and doesn't generate an error.
I have a feeling that this is because of the text not updating for some reason. I've tried to flush it with Invalidate() and Update(), but this isn't working. How to fix this?
Here is the code:
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "Start")
{
isEvil = true;
button1.Text = "Stop";
Thread t = new Thread(StartTyping);
t.Start(textBox1.Text);
}
else
{
isEvil = false;
button1.Text = "Start";
}
}
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
//////This does NOT work
//button1.Text = "Start";
//button1.Invalidate();
//button1.Update();
//button1.Refresh();
//Application.DoEvents();
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}
You have four answers telling you to update UI stuff from the UI thread, but none of them address the logic flow problem with your code.
The reason why it doesn't happen is because it only happens in the for-loop when isEvil is false. When does isEvil get set to false? Only when you click "Stop", and nowhere else.
If you want the button to go back to "Start" after the thread finishes, without clicking "Stop", then you need to add code after the loop to do that, independent of the value of isEvil: (piggybacking off of VoidMain's answer)
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
}
Now you have duplicated code, so you might want to split it off into a separate method.
You need to be on the UI thread to update the UI.
Try something called the SynchronizationContext. There are plenty of examples when you google it.
If you're in WPF or Silverlight, you could use the Dispatcher. Again, lots of examples if you search those keywords in google or StackOverflow.
You must update your controls from the UI thread. This is how you would do it for winforms.
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
button1.Invoke(new Action(() => button1.Text = "Start"));
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
This will block till button1 get's its text updated. If you don't want it to block, replace Invoke with BeginInvoke
Your best bet is to use a BackgroundWorker. It's a bit too wieldy to add a concise example here but there's a decent tutorial from O'Reilly
Something like this (not tested) should work:
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if(button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}

Categories

Resources