backgroundWorker exits as soon as starts, plus continues after cancel - c#

I'm having a lot of trouble with my background worker. I have a background worker to upload pictures (sometimes over 100) to tumblr. Naturally, this is extremely repetitive.
My problems are this:
1) As soon or a few seconds after I start my thread, backgroundWorker1_RunWorkerCompleted is called, as shown by it printing the text within that method/event.
2) When I cancel the thread, it calls backgroundWorker1_RunWorkerCompleted, as shown by it printing the text within it, however, files continue to keep uploading. It seems like the thread still exists.
I can't figure out what I'm doing wrong, I've been searching for the past few days and tried so many different methods. Here is my code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
var restClient = new RestClient("http://tumblr.com/api/write");
foreach (string item in queueBox.Items)
{
if (!backgroundWorker.CancellationPending)
{
var request = new RestRequest(Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddParameter("email", usernameBox.Text);
request.AddParameter("password", passwordBox.Text);
request.AddParameter("type", "photo");
byte[] byteArray = File.ReadAllBytes(FolderName + "\\" + item);
request.AddFile("data", byteArray, FolderName + "\\" + item);
var newItemToAvoidClosure = item;
restClient.ExecuteAsync(request, response => { doneBox.Invoke(new UpdateTextCallback(this.UpdateText), new object[] { newItemToAvoidClosure }); });
}
else
{
e.Cancel = true;
}
}
}
And here is my RunWorkerCompleted
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!(e.Cancelled))
{ //if not cancelled
if (!(e.Error == null))
{ //if above true, see if theres an error
doneBox.Items.Add("\r\nAn Error Ocurred: " + e.Error.Message + "\r\n. However, what you uploaded may have been successful up until this error." );
}
else
{ //else there is no error which means it was 100% successful
doneBox.Items.Add("Finished uploading! Go and check out your stuff on Tumblr! ^_^" );
}
}
else //else the user DID press cancel
{
doneBox.Items.Add("You pressed Cancel - However, the photos before you pressed cancel have been uploaded." );
}
}
Now, i'll try and explain what I think is happening in my DoWork,
since the for loop is what loops each photo upload, the if statement is in there so it can constantly check if cancellationpending is true. If it is, set e.Cancel to true to end the thread.
Also, I'm using ExecuteAsync to upload, so I was hoping I would be able to terminate the thread mid upload, so that photo doesn't upload.
Okay, I'm sorry, I know that this has been discussed before. I just can't figure out what I'm doing wrong though.
By the way, this is how I attempt to cancel:
public void stopButton_Click(object sender, EventArgs e)
{
doneBox.Items.Add("You pressed the stop button.");
backgroundWorker1.CancelAsync();
}
Thank you all.

Two things:
Firstly, you're trying to access the UI from the background worker:
request.AddParameter("email", usernameBox.Text);
You shouldn't do that. It sounds like you should be setting up all the information the background worker needs first, then starting it.
Secondly, the fact that you're calling ExecuteAsync is exactly why your background worker is completing immediately, but then the files are continuing to upload. You're basically queueing a load of work for the rest client to do asynchronously.
While are you using ExecuteAsync in the first place? You're already within a background thread - why not just do the work synchronously?

Related

WPF background-method to open window

I am currently working on a application that checks emails from an email-account via IMAP. This function is called every 5 seconds and it needs some time to work through.
private void CheckForRequests()
{
List<string[]> mails = CollectAllMails();
for (int i = 0; i < mails.Count; i++)
{
if (mails[i][0].Split('_')[0] == "request")
{
//INVITATION TO ME
if (mails[i][0].Split('_')[2] == username && mails[i][0].Split('_')[3] == "request")
{
DeleteMail(mails[i][0]);
MessageBoxResult result = MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (result == MessageBoxResult.Yes)
{
DeleteMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_" + mails[i][0].Split('_')[3]);
SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_accept", "");
ChatWindow chat = new ChatWindow();
chat.ShowDialog();
//do open chat window
}
else if (result == MessageBoxResult.No)
{
DeleteMail("request_" + mails[i][0].Split('_')[1] + mails[i][0].Split('_')[2]);
SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_decline", "");
}
}
//ACCEPTION FROM ANOTHER DUDE
else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "accept")
{
ChatWindow chat = new ChatWindow();
chat.ShowDialog();
}
//REJECTION FROM ANOTHER DUDE
else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "decline")
{
MessageBox.Show("Your invitation was declined.", "Sorry", MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
else if (mails[i][0].Split('_')[0] == "somethingelse")
{
}
}
}
My loop calls this method every 5 seconds and in this time I can't write or do anything in my application. Im pretty sure that I have to use a Thread or Task to solve the problem but I didn't found out how to do this related to my case. When I call the method in a Task and I click Yes it crashes and says something like it has to be a STA-Thread... In this case I don't even want to access the GUI with the thread, I just want to check the mails and if the method found something it should do something like break from the Task and call a method (NOT from the Task).
What would be the cleanest solution for this problem?
Your threading issue is caused by you trying to do UI stuff on a non-UI thread. You can solve this problem by using Dispatcher.Invoke whenever you call UI stuff like this
Application.Current.Dispatcher.Invoke(() =>
{
// Your stuff here
});
So in your case you'd have something like this
void CheckForRequests()
{
// Do stuff
Application.Current.Dispatcher.Invoke(() =>
{
// Open your message box
});
// Do more stuff
Application.Current.Dispatcher.Invoke(() =>
{
// Open another message box
});
}
Your are Right about needing to use Threads
While #Gareth is right about the need to use a dispatcher to correctly access elements across theads, i actually don't see any threading in your code, though the error message clearly proves you have attempted some.
to implement the threading you have various options
firstly you can do this directly via Tasks or the older Thread classes
this would simply be done like so
private void CheckForRequestsAsync()=> Task.Run(()=>CheckForRequests());
this will instantly create and start a task that will perform CheckForRequests in a separate thread freeing the GUI to continues its work on the GUI, however this is a very basic implementation and likely will need further enhancement before reliably meeting your needs
another option is to take advantage of some of the newer features in .Net and use the async keyword
if you declare CheckForRequests as private async void CheckForRequests (object sender, EventArgs e) then the void will be automatically be converted into a task which can be fired off by an event hander as a async task say of a Timer
eg
Timer timer = new Timer(5000);
timer.Elapsed += CheckForRequests; //where CheckForRequests has the async keyword
timer.Start();
combine this with the dispatcher information #Gareth suggested on anything that throws a cross thread access exception and you should be ready
this would look something like this:
MessageBoxResult result = Dispatcher.Invoke(() =>
MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
);
Note if you use async with out using the await keyword then you will get warnings that your thread may exit before any worker threads have completed, however this is just a warning as if you aren't calling any worker threads inside your method or you don't need them to complete before exiting then there is no harm done
Finally
one of the comments suggested using DispatcherTimer rather than a Timer, i would not suggest this as every time the timer ticks, it will run your code in the GUI thread locking it just as you are already seen, the DispatcherTimer is best used when the timer heavily changes the GUI and is quick running
though if you redefined your code then you could user a DispatcherTimer by breaking out the gui element from the slow running process
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += (s,e)=>{
if( MessageBox.Show("Do you accept the request", "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes) == MessageBoxResult.Yes)
{
Task.Run(()=>CheckForRequests());
}
}
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();

Timer issue in C# with threads

I am developing a skype-like application, I have an external DLL that do most of the work and fires events handled in my class ip2ip, one of this events is incoming_call fired when there is an incoming call as the name suggest. I'm trying to manage missed calls.
Now this is the relevant part of the code in this class:
private void ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = null;
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
while (AcceptIncomingCall.HasValue == false) Thread.Sleep(100);
if(AcceptIncomingCall.Value == true)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall = null;
IncomingCall = false;
}
}
IncomingCall is a property generating a PropertyChangedEvent, wich is captured in my main class where I have this code:
private void ip2ip_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && string.IsNullOrEmpty(e.PropertyName) == false)
{
..............
if (e.PropertyName.Equals("IncomingCall") && ip2ip.IncomingCall == true)
{
Invoke(new MethodInvoker(delegate
{
pnlCalling.Visible = true;
aTimer.Start();
}));
}
................
}
}
public Form1()
{
.......
aTimer = new System.Windows.Forms.Timer();
aTimer.Interval = 10000;
aTimer.Tick += aTimer_Tick;
}
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = false;
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = true;
}
I need the timer to manage the missed call, when there is an incoming call a panel appears, with buttons to accept/reject the call. If the user waits too much the call is considered rejected (missed).
In this way it doesn't work, probably I'm doing something wrong with the timer, as without any timer everything works. I also tried the timer of the class System.Timers with same results. Any Idea?
EDIT
This is my expectation, there is an incoming call so the event ics_IncomingCall is fired, IncomingCall=true cause the execution to go to the main class (we are still in same thread, I see it debugging step by step in VS) where is invoked in the GUI thread the panel to be visible and started the timer, now we have one thread where a while loop block the execution until in the other thread user do something (accept/reject).
The problem exist when the user accept the call, the code after the while loop is always executed, the caller has no problem at all and receive the stream, but in the receiver (who receive the stream as I verified in wireshark) the DLL (who is responsible to show the incoming video) fails to do its job for some reason unknown to me but caused by the timer.
It is unfortunate your question does not include a good, minimal, complete code example that reliably reproduces the problem. Having such a code example would make it much more practical for someone to provide a useful answer.
That said, as explained by commenter varocarbas, your fundamental problem appears to be that you have blocked the UI thread (with the while loop), while at the same time hoping for the UI thread to handle other activity (such as the timer's tick event). In fact, you are also preventing the button click from having an effect. The button Click event handlers can't execute either, while the UI thread is blocked.
One possible way to fix this would be to use a TaskCompletionSource<T> to provide the ics_IncomingCall() with a waitable object, which the buttons and timer can use to signal. For example:
// Change from "bool?" to this:
private TaskCompletionSource<bool> AcceptIncomingCall;
public void HandleCall(bool accept)
{
AcceptIncomingCall.SetResult(accept);
}
private async Task ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = new TaskCompletionSource<bool>();
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
if (await AcceptIncomingCall.Task)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall.Dispose();
IncomingCall = false;
}
}
and:
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
This causes the ics_IncomingCall() method to return when it reaches the await statement, allowing its thread to continue executing. The button Click event handlers will call back to the public method that encapsulates your field (public fields are very dangerous and should be avoided in almost all situations), setting the result value for the TaskCompletionSource object that is being awaited.
Once the result value has been set, this will cause the framework to resume executing your ics_IncomingCall() method where it left off, but now with the value returned from the button Click event handlers. I.e. true if the user clicked the btnOk and false if they clicked btnNo or the timer interval elapsed.
Note that this changes the signature of your ics_IncomingCall() method, which will force a change to the caller. The best way to handle that will be to change the caller as well, to be async and to use await ics_IncomingCall(...). That will of course force a change in its caller, and its caller's caller, and so on. But you need to release the UI thread, and this is the best way to do it. Hopefully you don't have a lot of callers to change, but even if you do, this is the way to go.
If the above does not seem to address your problem, please provide a good MCVE. Note that a good MCVE is both complete and minimal. You will want to remove from the example any code that is not strictly required to reproduce the problem. At the same time, make sure someone can copy and paste the code into an empty project and have it run with at most very minimal effort, and preferably none at all.

C# Keithley IVI Measure is slow

I'm using the Keithley 2100 digital multimeter to gather VAC readings for a piece of calibration software i'm writting. I've made a small test program to gather some data on Keithley's IVI Class Library that can be downloaded from their website.
I'm running a background worker which is gathering outputs from the multimeter, see code;
private void readButton_Click(object sender, EventArgs e) // gather readings
{
if (!backgroundWorker1.IsBusy)
{
address = Ke2100FunctionEnum.Ke2100FunctionACVolts;
range = Double.Parse(textBox2.Text);
resolution = Double.Parse(textBox3.Text);
backgroundWorker1.RunWorkerAsync();
}
else
{
MessageBox.Show("Task already enabled");
}
}
This is my gather reading button, it checks to make sure the background worker isn't busy, then runs the worker.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
Invoke(new Action(() =>
{
ACResult = ke2100Device.Measure(address, range, resolution);
richTextBox1.Text += ACResult.ToString() + "\n";
}));
if(backgroundWorker1.CancellationPending)
{
backgroundWorker1.Dispose();
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
It takes around a second for the ke2100Device.Measure function to process one reading, but in this time period the whole program becomes unresponsive, which I just can't have in my program. I've loaded up the task manager to see if any of my cores are on 100%, as it seems like quite an intensive function, but my usage is just fine.
I'm a little stumped on how to get fix this issue. I've commented out the ke2100Device.Measure function and just had the rich text box add random numbers, this works as expected with no unresponsiveness.
The only ideas I have just seem to be another way of doing the same thing... Coffee break!
-- Edit --
Updated code;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += ACResult.ToString() + "\n"; }));
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
Though if I run this debug code to check my bgw;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
//ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += 0 + "\n"; })); //ACResult.ToString()
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
Then I don't get any hangs, perhaps there is an actual issue with the Measure function? Could it be doing something I'm not fully realising or seeing?
The call to Measure should be outside of the Invoke action. Calling it inside the Invoke effectively runs it on the UI thread, rendering your background worker meaningless.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += ACResult.ToString() + "\n"; }));
if(backgroundWorker1.CancellationPending)
{
//backgroundWorker1.Dispose(); // I don't think you want this here!
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
As you want to notify of the state on each measure you probably want to use the the ProgressChanged() event that the BackgroundWorker exposes. You can set the UserState property when you call ReportProgress().
Doing this will mean you don't have to think about whether to call Invoke() or not as the BackgroundWorker will hide this implementation detail for you.
Also, if you update your GUI every 10ms i.e. 100 times per second you user probably won't be able to notice the different updates. You might want to change this value to be configurable and then play with it to get the desired refresh rate.
I have found the issue. I went straight into the basics of how the device communicates with my laptop, and found out that it uses SCPI commands, so from this point I started making two really simple functions that creates a connection, and then sends a command to the multimeter.
After this point I realised that all commands being sent to the multimeter and back are done on the command line, which then lead me to believe that the command line and GUI thread are actually the same thread, which would explain why the whole program would hang when trying to read data from my device.
How did I fix this? Easily, I put my app on another thread before loading it up, see code!
Thread applicationThread = new Thread(() => Application.Run(new Form1()));
applicationThread.Start();
No more hanging! I hope this can help other people down the line. Thanks for the help guys and girls!

Program doesn't increment progress bar while waiting for thread to exit

I have a c# form app that serves as an UI and executes an external exe. I want to make a progress bar increment until the external exe finishes executing. so i have the following code:
// create thread and Start external process
Thread MyNewThread = new Thread(new ThreadStart(startmodule));
MyNewThread.Start();
do
{
if (progressBar1.Value < 100)
{
progressBar1.Value++;
}
} while (MyNewThread.IsAlive);
label5.Text = "Status: Done";
// startmodule()
void startmodule()
{
ProcessObj = new Process();
ProcessObj.StartInfo.FileName = ApplicationPath;
ProcessObj.StartInfo.Arguments = ApplicationArguments;
ProcessObj.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
ProcessObj.Start();
}
Instead it fills the bar up instantly and shows "Done" message but the external exe (AppPath) still runs in the background.
Please post some ideas im stuck. i don't know whats wrong. Thank you for your time.
You cannot make this work, you cannot guess how long the process will take. Set the ProgressBar.Style property to Marquee. Set it Visible property to true when you start the process. Use the Process.Exited event to set it back to false. Like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Visible = false;
}
private void ButtonRunProcess_Click(object sender, EventArgs e) {
var ProcessObj = new Process();
ProcessObj.SynchronizingObject = this;
ProcessObj.EnableRaisingEvents = true;
ProcessObj.Exited += new EventHandler(ProcessObj_Exited);
ProcessObj.StartInfo.FileName = #"c:\windows\notepad.exe";
// etc...
ProcessObj.Start();
progressBar1.Visible = true;
}
void ProcessObj_Exited(object sender, EventArgs e) {
progressBar1.Visible = false;
}
}
Well the loop is being run so fast, that it reaches 100% before your task is actually completed. The condition that the loop is being check for (The thread being alive) is going to be true until your task is completed, but the loop is causing the progress bar to fill up prematurely.
In order to run a progress bar you have to be able to quantify the progress of the long running task. You have nothing in the code that attempts to quantify this.
You would need there to be communication between the two processes in order to make this progress bar work well. In other words the external process needs to send messages back to the parent app informing the parent app of the measure of progress. Now, that can be hard to achieve so a marquee style progress bar may be more appropriate.
Finally i got some "free" time to test the backgroundworker as suggested above. i can say it's the best solution and it doesn't freeze the UI. Example implementation follows:
preparemodule()
{
ProcessObj = new Process();
ProcessObj.StartInfo.FileName = ApplicationPath;
ProcessObj.StartInfo.Arguments = ApplicationArguments;
}
void run_Click(object sender, EventArgs e)
{
preparemodule();
backgroundWorker1.RunWorkerAsync(ProcessObj);
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int i=0;
ProcessObj.Start();
while (checkifexists("notepad", 0) == true)
{
i++;
label5.Text = "Status: notepad running... " + progressBar1.Value.ToString() + "%";
Thread.Sleep(3000);
backgroundWorker1.ReportProgress(i);
if ((backgroundWorker1.CancellationPending == true))
{
e.Cancel = true;
}
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage <= 100)
{
progressBar1.Value = e.ProgressPercentage;
}
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label5.Text = "Status: Done";
}
void cancel_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
As you see we can even cancel it. and by checking if notepad is running we can increment out progressbar. Dont forget to enable bgWorker's "reportsprogress" and "supportscancellation" properties somewhere in your code. i hope it helps someone.
First, #Tim answer is right about what is happening.
If you can control the external app, make a way to it communicate with the main process telling the current state and update the progress bar according to these messages.
If is not possible, try to estimate the execution time and set the progress according to the execution time. This is valid if it performs always in same time for the same task.
Background worker thread was designed for this sort of thing.
It has an event you can fire while processing something, you handle it and update your progress bar. Course as noted by others you don't seem to have any measure of progress, just some time has passed, so it's not really an indication of progress you want but some sort of "I'm busy" animation, if you use a progress bar for that you get all sorts of issues that drive the UI boys mad, like it never gets to 100%, or it gets to 100% well before the operation has finished, or even cycles round.
So if you can indicate some progress from the thread, e.g if you are looping through X items fire the progress event every 10% of X. Use a Background worker thread.
If you can't don't use a progress bar kick the thread off an make some animated control visible. When the thread finishes make the animation invisible again. What and how of the animation is up to you and your UI boys.

C# cancelling DoWork of background worker

C# 2008
I am using the code below to login to a softphone. However, the login progess is a long process as there are many things that have to be initialized and checks to be made, I have only put a few on here, as it would make the code to long to post.
In the code below I am checking if the CancellationPending if the CancelAsync has been called in my cancel button click event, before doing each check. Is this correct? Also if the check fails I also call the CancelAsync and set the e.Cancel to true.
I would like to know if my method I have used here is the best method to use.
Many thanks for any advice,
private void bgwProcessLogin_DoWork(object sender, DoWorkEventArgs e)
{
/*
* Perform at test to see if the background worker has been
* cancelled by the user before attemping to continue to login.
*
* Cancel background worker on any failed attemp to login
*/
// Start with cancel being false as to reset this if cancel has been set to true
// in the cancel button.
e.Cancel = false;
NetworkingTest connection_test = new NetworkingTest();
if (!this.bgwProcessLogin.CancellationPending)
{
// Check local LAN or Wireless connection
if (!connection_test.IsNetworkConnected())
{
// Update label
if (this.lblRegistering.InvokeRequired)
{
this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(UpdateRegisterLabel), "No network connection");
}
else
{
this.lblRegistering.Text = "No network connection";
}
// Failed attemp
this.bgwProcessLogin.CancelAsync();
e.Cancel = true;
return;
}
// Report current progress
this.bgwProcessLogin.ReportProgress(0, "Network connected");
}
else
{
// User cancelled
e.Cancel = true;
return;
}
// Test if access to Server is available
if (!this.bgwProcessLogin.CancellationPending)
{
if (!connection_test.IsSIPServerAvailable())
{
// Update label
if (this.lblRegistering.InvokeRequired)
{
this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(UpdateRegisterLabel), "Server unavailable");
}
else
{
this.lblRegistering.Text = "Server unavailable";
}
// Failed attemp
this.bgwProcessLogin.CancelAsync();
e.Cancel = true;
return;
}
// Report current progress
this.bgwProcessLogin.ReportProgress(1, "Server available");
}
else
{
// User cancelled
e.Cancel = true;
return;
}
.
.
.
}
private void bgwProcessLogin_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Check for any errors
if (e.Error == null)
{
if (e.Cancelled)
{
// User cancelled login or login failed
}
else
{
// Login completed successfully
}
}
else
{
// Something failed display error
this.statusDisplay1.CallStatus = e.Error.Message;
}
}
private void bgwProcessLogin_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.lblRegistering.Text = e.UserState.ToString();
}
private void btnCancel_Click(object sender, EventArgs e)
{
// Cancel the logging in process
this.bgwProcessLogin.CancelAsync();
this.lblRegistering.Text = "Logged out";
}
There is maybe only one problem: if one of the operation in DoWork event handler would last for a long time. In this case you could abort your pending operation ONLY after that operation finished. If all operations in DoWork event can't last very long (for instance, no more than 5 seconds), its all OK, but if one of the operations can last for long time (5 minutes, for instance) in this case user have to wait until this operation finished.
If DoWork contains long lasting operations you can use something like AbortableBackgroundWorker. Something like this:
public class AbortableBackgroundWorker : BackgroundWorker
{
private Thread workerThread;
protected override void OnDoWork(DoWorkEventArgs e)
{
workerThread = Thread.CurrentThread;
try
{
base.OnDoWork(e);
}
catch (ThreadAbortException)
{
e.Cancel = true; //We must set Cancel property to true!
Thread.ResetAbort(); //Prevents ThreadAbortException propagation
}
}
public void Abort()
{
if (workerThread != null)
{
workerThread.Abort();
workerThread = null;
}
}
}
In this case you can truly abort pending operations, but you also have some restrictions (for more information about aborting managed thread and some restrictions see Plumbing the Depths of the ThreadAbortException Using Rotor).
P.S. I agree with Oliver that you should wrap InvokeRequired in more usable form.
You are doing it the right way, I believe. You will find thread members that allow you to terminate or abort a thread, but you don't want to use them for something like this. It might look a little weird to have all of the "cancelled" checks in your code, but that allows you to control exactly when you exit your thread. If you were to "rudely" abort the worker thread, the thread has no control of when it exits, and there could be corrupted state.
Within your DoWork() function you wrote .... Depending on how many tasks of the same structure are coming like the displayed two one, you could refactor this structure into an own method, giving the changing parts as parameters.
Also this InvokeRequired if-else branch has doubled the output string. A little search here on stackoverflow or on the web should show you a pattern to accomplish this doubling.
Evernything else looks quite good.
There is one thing I don't need to call the this.bgwProcessLogin.CancelAsync(); as you can just set this e.Cancel = true;

Categories

Resources