Backgroundworker.CancelAsync() not working - c#

I have a backgroundworker that runs a single process. I want to be able to cancel the processing while it's going, but when I call the CancelAsync() method, it never actually cancels. Where am I wrong?
Here's the DoWork() method:
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker b = sender as BackgroundWorker;
if (b != null)
{
if (!b.CancellationPending)
{
try
{
// Let's run the process as a backgroundworker so we have the ability to cancel the search, and/or be able to view results while it's still searching
ProcessParameters pp = e.Argument as ProcessParameters;
if (pp.DoReplace)
results = FindReplace.FindReplace.FindAndReplace(pp.PathToSearch, pp.FindText, pp.ReplaceText, pp.UseRegularExpressions, pp.IncludeList, pp.ExcludeList, pp.RecurseSubdirectories, pp.IgnoreCase);
else
results = FindReplace.FindReplace.Find(pp.PathToSearch, pp.FindText, pp.UseRegularExpressions, pp.IncludeList, pp.ExcludeList, pp.RecurseSubdirectories, pp.IgnoreCase);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
else
{
// Cancel was clicked
e.Cancel = true;
}
}
}
Here's the method that starts the processing:
private void btnGo_Click(object sender, EventArgs e)
{
if (btnGo.Text == "Cancel")
{
if (DialogResult.Yes == MessageBox.Show("Are you sure you wish to cancel?", "Cancel Requested", MessageBoxButtons.YesNo, MessageBoxIcon.Question))
bgw.CancelAsync();
return;
}
if (tbFind.Text.Length == 0)
{
MessageBox.Show("Find text is not valid.");
return;
}
tbFound.Text = String.Empty;
tbFoundInThisFile.Text = String.Empty;
lvResults.Items.Clear();
includeList = null;
excludeList = null;
results = null;
if (radDirectory.Checked && !radFile.Checked)
{
includeList = BuildIncludeExcludeList(tbIncludeFiles.Text);
excludeList = BuildIncludeExcludeList(tbExcludeFiles.Text);
}
ProcessParameters pp = null;
if (chkReplace.Checked)
pp = new ProcessParameters(tbPath.Text, tbFind.Text, tbReplace.Text, chkUseRegEx.Checked, includeList, excludeList, chkRecursion.Checked, chkIgnoreCase.Checked, true);
else
pp = new ProcessParameters(tbPath.Text, tbFind.Text, chkUseRegEx.Checked, includeList, excludeList, chkRecursion.Checked, chkIgnoreCase.Checked, false);
bgw.RunWorkerAsync(pp);
// Toggle fields to locked while it's running
btnGo.Text = "Cancel";
}
And here's the WorkerCompleted() event:
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
btnGo.Text = "Go";
string message = String.Empty;
const string caption = "FindAndReplace is Complete";
if (!e.Cancelled)
{
if (results != null)
{
tbFound.Text = results.Found.ToString();
tbSearched.Text = results.FilesSearched.ToString();
tbSkipped.Text = results.FilesSkipped.ToString();
message = String.Format("Search finished resulting in {0} match(es).", results.Found);
}
else
message = "The FindAndReplace results were empty. The process was cancelled or there was an error during operation.";
}
else
message = "The FindAndReplace process was cancelled.";
if (e.Error != null)
message += String.Format("{0}{0}There was an error during processing: {1}", Environment.NewLine, e.Error);
MessageBox.Show(message, caption);
}

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 ran in the background must periodically check
this property and handle the cancellation itself.
Read more here

You don't really have a way to cancel the operation. The problem is that this code
if (pp.DoReplace)
results = FindReplace.FindReplace.FindAndReplace(pp.PathToSearch, pp.FindText, pp.ReplaceText, pp.UseRegularExpressions, pp.IncludeList, pp.ExcludeList, pp.RecurseSubdirectories, pp.IgnoreCase);
else
results = FindReplace.FindReplace.Find(pp.PathToSearch, pp.FindText, pp.UseRegularExpressions, pp.IncludeList, pp.ExcludeList, pp.RecurseSubdirectories, pp.IgnoreCase);
doesn't have any way to break once it starts running. So, what winds up happening is that you hit cancel, but the cancel never registers unless you've canceled before the action begins. Once that action is complete, the DoWork method returns successfully and the backgroundworker never triggers the cancellation.
EDIT: If you have a way to break the text up into smaller chunks that can then be "searched and replaced", you could loop through those segments and perform a cancellation check on each loop. You'd need to make sure that you account for the search string being across those break boundaries, though, so it may actually take LONGER to allow for cancellation.

Your code is right, but if you carefully read it again, you will see that once the background worker starts, soon it goes beyond the cancel check. After that even if you try to cancel, it won't work any more.
You have to redesign your search and replace algorithm to include the cancel check too, so as to support cancellation as you wished.

Related

Why doesn't the WPF Progressbar Animation Start On Function Call?

I'm working on my first WPF project and I'm trying to add a progress bar for a lengthy function.
I have added message boxes to notify me on function success/error. I need to use IsIndeterminate type progress bar.
The RunWorkerAsync() line also gets called properly but then when the DoWork function call the lengthy function inside it, the animation doesn't work.When the function is over and the message-box pops up the animation works fine.
private void ButtonPipeline_Click(object sender, RoutedEventArgs e)
{
pbStatus.IsIndeterminate = true;
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += worker_DoWorkPipeline_Click;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Sync Interrupted.", "Message", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
pbStatus.IsIndeterminate = false;
MessageBox.Show("Synced Completed.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
pbStatus.Value = 0;
}
}
void worker_DoWorkPipeline_Click(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
var worker = sender as BackgroundWorker;
try
{
var pipeline = GetPipeline(); //THis function throws "The calling thread cannot access this object because a different thread owns it"
if (pipeline.Name.Equals("<None>"))
{
MessageBox.Show("Please select a pipeline.", "Missing Data", MessageBoxButton.OK, MessageBoxImage.Warning);
worker.CancelAsync();
}
else
{
aLongFunction(pipeline);
MessageBox.Show("Pipeline: " + pipeline + Environment.NewLine + "Successfully Synced.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
}
if (worker.CancellationPending == true)
{
pbStatus.IsIndeterminate = false;
pbStatus.Value = 0;
e.Cancel = true;
return;
}
}
catch (Exception ex)
{
worker.CancelAsync();
MessageBox.Show(ex.InnerException.ToString(), "Exception Occoured!", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void aLongFunction(Pipeline pipeline)
{
var session = new SynchronizationSession(pipeline);
session.Run();
MessageBox.Show("Successfully Synced.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
}
public void Run()
{
anotherFunction();
}
private Pipeline GetPipeline()
{
var pipeline = (Pipeline)DropdownSyncPipeline.SelectedItem; //This throws and error since trying to access another UI Object.
if (null != pipeline)
{
if (0 == pipeline.Id)
{
var p = PerfOtherPipeline.Text;
if (!string.IsNullOrEmpty(p)) pipeline = BrokerDataCache.Pipelines.Find(p_ => p.Equals(p_.Name));
}
}
return pipeline;
}
BackgroundWorker is a tool to help abstract the handling of threads, but the code you have posted uses it in an odd way:
The event attached to it at DoWork (worker_DoWorkPipeline_Click) will run in a different thread than the UI, however since you have the entire method wrapped in an invoke to Dispatcher, all of aLongFunction will run on the same thread as the UI.
From your description of the error, the problem is that the progress bar stops animating, right? The reason for this is that the UI is not responsive whenever code is run on its thread using Dispatcher.Invoke.
The solution to this issue will be to remove the Invoke method from the worker_DoWorkPipeline_Click.
However if the code in SynchronizationSession accesses any UI objects in your project, you can expect an exception to occur for this since WPF UI objects generally can only be accessed on the same thread. You haven't provided the code for the function so we cannot tell from what you have provided.
If the SynchronizationSession, or anything else running within the Backgroundworker needs to update the UI, use Dispatcher.Invoke only for the part of the code that needs to update the UI. This will avoid blocking the UI for the entirety of the operation but only do so when necessary.

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.

Awaiting Asynchronous function inside FormClosing Event

I'm having a problem where I cannot await an asynchronous function inside of the FormClosing event which will determine whether the form close should continue. I have created a simple example that prompts you to save unsaved changes if you close without saving (much like with notepad or microsoft word). The problem I ran into is that when I await the asynchronous Save function, it proceeds to close the form before the save function has completed, then it comes back to the closing function when it is done and tries to continue. My only solution is to cancel the closing event before calling SaveAsync, then if the save is successful it will call the form.Close() function. I'm hoping there is a cleaner way of handling this situation.
To replicate the scenario, create a form with a text box (txtValue), a checkbox (cbFail), and a button (btnSave). Here is the code for the form.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestZ
{
public partial class Form1 : Form
{
string cleanValue = "";
public Form1()
{
InitializeComponent();
}
public bool HasChanges()
{
return (txtValue.Text != cleanValue);
}
public void ResetChangeState()
{
cleanValue = txtValue.Text;
}
private async void btnSave_Click(object sender, EventArgs e)
{
//Save without immediate concern of the result
await SaveAsync();
}
private async Task<bool> SaveAsync()
{
this.Cursor = Cursors.WaitCursor;
btnSave.Enabled = false;
txtValue.Enabled = false;
cbFail.Enabled = false;
Task<bool> work = Task<bool>.Factory.StartNew(() =>
{
//Work to do on a background thread
System.Threading.Thread.Sleep(3000); //Pretend to work hard.
if (cbFail.Checked)
{
MessageBox.Show("Save Failed.");
return false;
}
else
{
//The value is saved into the database, mark current form state as "clean"
MessageBox.Show("Save Succeeded.");
ResetChangeState();
return true;
}
});
bool retval = await work;
btnSave.Enabled = true;
txtValue.Enabled = true;
cbFail.Enabled = true;
this.Cursor = Cursors.Default;
return retval;
}
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (HasChanges())
{
DialogResult result = MessageBox.Show("There are unsaved changes. Do you want to save before closing?", "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == System.Windows.Forms.DialogResult.Yes)
{
//This is how I want to handle it - But it closes the form while it should be waiting for the Save() to complete.
//bool SaveSuccessful = await Save();
//if (!SaveSuccessful)
//{
// e.Cancel = true;
//}
//This is how I have to handle it:
e.Cancel = true;
bool SaveSuccessful = await SaveAsync();
if (SaveSuccessful)
{
this.Close();
}
}
else if (result == System.Windows.Forms.DialogResult.Cancel)
{
e.Cancel = true;
}
//If they hit "No", just close the form.
}
}
}
}
Edit 05/23/2013
Its understandable that people would ask me why I would be trying to
do this. The data classes in our libraries will often have Save,
Load, New, Delete functions that are designed to be run asynchronously
(See SaveAsync as an example). I do not actually care that much about
running the function asynchronously in the FormClosing Event specifically. But if
the user wants to save before closing the form, I need it to wait and
see if the save succeds or not. If the save fails, then I want it to
cancel the form closing event. I'm just looking for the cleanest way to
handle this.
The best answer, in my opinion, is to cancel the Form from closing. Always. Cancel it, display your dialog however you want, and once the user is done with the dialog, programatically close the Form.
Here's what I do:
async void Window_Closing(object sender, CancelEventArgs args)
{
var w = (Window)sender;
var h = (ObjectViewModelHost)w.Content;
var v = h.ViewModel;
if (v != null &&
v.IsDirty)
{
args.Cancel = true;
w.IsEnabled = false;
// caller returns and window stays open
await Task.Yield();
var c = await interaction.ConfirmAsync(
"Close",
"You have unsaved changes in this window. If you exit they will be discarded.",
w);
if (c)
w.Close();
// doesn't matter if it's closed
w.IsEnabled = true;
}
}
It is important to note the call to await Task.Yield(). It would not be necessary if the async method being called always executed asynchronously. However, if the method has any synchronous paths (ie. null-check and return, etc...) the Window_Closing event will never finish execution and the call to w.Close() will throw an exception.
Dialogs handle messages while still keeping the current method on the stack.
You could show a "Saving..." Dialog in your FormClosing handler, and run the actual saving-operation in a new task, which programmatically closes the dialog once it's done.
Keep in mind that SaveAsync is running in a non-UI Thread, and needs to marshal any access UI elements via Control.Invoke (see call to decoy.Hide below). Best would probably be to extract any data from controls beforehand, and only use variables in the task.
protected override void OnFormClosing(FormClosingEventArgs e)
{
Form decoy = new Form()
{
ControlBox = false,
StartPosition = FormStartPosition.CenterParent,
Size = new Size(300, 100),
Text = Text, // current window caption
};
Label label = new Label()
{
Text = "Saving...",
TextAlign = ContentAlignment.MiddleCenter,
Dock = DockStyle.Fill,
};
decoy.Controls.Add(label);
var t = Task.Run(async () =>
{
try
{
// keep form open if saving fails
e.Cancel = !await SaveAsync();
}
finally
{
decoy.Invoke(new MethodInvoker(decoy.Hide));
}
});
decoy.ShowDialog(this);
t.Wait(); //TODO: handle Exceptions
}
You can't keep your form from closing with async/await. And you can get strange results.
What I would do is creating a Thread and setting its IsBackground property to false (which is false by default) to keep the process alive while form is closing.
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = false;
new Thread(() => {
Thread.Sleep(5000); //replace this line to save some data.....
MessageBox.Show("EXITED");
}).Start();
base.OnClosing(e);
}
I had a similar issue when I tried to handle all of the close event async. I believe it is because there is nothing to block the main thread from moving forward with the actual FormClosingEvents. Just put some inline code after the await and it solves the problem. In my case I save the current state no matter the response (while waiting for the response). You could easily have the task return a current state ready to be saved appropriately once the user responds.
This worked for me: Spin off task, ask exit confirmation, await task, some inline code.
Task myNewTask = SaveMyCurrentStateTask(); //This takes a little while so I want it async in the background
DialogResult exitResponse = MessageBox.Show("Are you sure you want to Exit MYAPPNAME? ", "Exit Application?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
await myNewTask;
if (exitResponse == DialogResult.Yes)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
I needed to abort closing the form if an exeption was raised during the execution of an async method.
I'm actually using a Task.Run with .Wait()
private void Example_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
Task.Run(async () => await CreateAsync(listDomains)).Wait();
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}", "Attention", MessageBoxButtons.OK, MessageBoxIcon.Error);
e.Cancel = true;
}
}
Why does asynchronous behavior have to be involved? It sounds like something that has to happen in a linear fashion.. I find the simplest solution is usually the right one.
Alternatively to my code below, you could have the main thread sleep for a second or two, and have the async thread set a flag in the main thread.
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (HasChanges())
{
DialogResult result = MessageBox.Show("There are unsaved changes. Do you want to save before closing?", "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
e.Cancel = true;
if(!Save())
{
MessageBox.Show("Your work could not be saved. Check your input/config and try again");
e.Cancel = true;
}
}
else if (result == DialogResult.Cancel)
{
e.Cancel = true;
} } }

Background worker cancelation

I have a timer and a background worker, which initiates on every timer tick. Sometimes I need for user to stop this workflow and call that same bg worker from a button click.
Since it is in Silverlight it is all async. BG worker make async Webservice call, which asynchronously returns data.
I have done this, but it just feels wrong. What are best ways to handle that kind of situation?
button_click_event(..)
{
_loadTimer.Stop();
_worker.CancelAsync();
_worker.RunWorkerAsync();
}
WebService call
public void GetUserStats(DateTime start, DateTime end, Action<IEnumerable<IUserStats>, Exception> callback)
{
_context.GetUserStatsCompleted += ContextGetUserStatsCompleted;
_context.GetUserStatsAsync(start,end,callback);
}
void ContextGetUserStatsCompleted(object sender, GetUserStatsCompletedEventArgs e)
{
var callback = e.UserState as Action<IEnumerable<IUserStats>, Exception>;
Exception error = null;
var result = new ObservableCollection<IUserStats>();
if (e.Error == null)
{
result = e.Result;
}
else
{
error = e.Error;
}
_context.GetUserStatsCompleted -= ContextGetUserStatsCompleted;
callback(result, error);
}
and my worker
void WorkerDoWork(object sender, DoWorkEventArgs e)
{
TicketService.GetUserStats(StartDate, EndDate, (result, error) =>
{
StreamHolder = result;
});
}
Firs of all, you should always check to see if your worker is running, prior to attempting to run it again. If you don't then it's possible that your application will throw an exception.
if(!_worker.IsBusy)
{
_worker.RunWorkerAsync();
}
Second of all, just calling CancelAsync() is not enough to cancel the current operation of the background worker. You will have to add code to the background worker's DoWork event handler. (In your case WorkerDoWork)
if(_worker.CancelationPending == true)
{
e.Cancel = true;
return;
}
You can read more about the proper way to use a background worker here:
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

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