C# - App Crash When Using Ping event - c#

i'm working with ping Librarry in net 3.5 to check the presence of IP.
take a look at the code below:
public void PingIP(string IP)
{
var ping = new Ping();
ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted); //here the event handler of ping
ping.SendAsync(IP,"a");
}
void ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
if (e.Reply.Status == IPStatus.Success)
{
//On Ping Success
}
}
Then i execute the code through Thread or backgroundworker.
private void CheckSomeIP()
{
for (int a = 1; a <= 255; a++)
{
PingIP("192.168.1." + a);
}
}
System.Threading.Thread checkip = new System.Threading.Thread(CheckSomeIP);
checkip.Start();
Well, here is the problem:
If i start the thread then i would to close the application (Close with Controlbox at corner), i would get "App Crash"
although i have closed/abort the thread.
I think the problem is event handler? as they still working when i'm closing the Application so that i will get "App Crash"
What would be the best way to solve this case?

I think, on a successfull Ping, you are trying to update the interface from within the Thread, which will cause an CrossThreadingOperation exception.
Search the Web for ThreadSave / delegates:
public void PingIP(string IP)
{
var ping = new Ping();
ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted); //here the event handler of ping
ping.SendAsync(IP,"a");
}
delegate void updateTextBoxFromThread(String Text);
void updateTextBox(String Text){
if (this.textbox1.InvokeRequired){
//textbox created by other thread.
updateTextBoxFromThread d = new updateTextBoxFromThread(updateTextBox);
this.invoke(d, new object[] {Text});
}else{
//running on same thread. - invoking the delegate will lead to this part.
this.textbox1.text = Text;
}
}
void ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
if (e.Reply.Status == IPStatus.Success)
{
updateTextBox(Text);
}
}
Also on "quitting" the application, you may want to cancel al running threads. Therefore you need to keep the reference on every thread you start somewhere in your application. in the formClosing-Event of your Main-Form, you can force all (running) threads to stop.

Related

Multithread C# serialPort1_DataReceived event does not fire

I read/write data to serial port and I want to see reading on listbox right away. I created a new thread to send command to serial port. I keep the main thread empty, so, it can update the UI and also serial port event handler wont be interrupted with something else.(I am not sure is it right approach?)
The following code works with while (!dataRecieved) { Thread.Sleep(4000); } but does not works with while (!dataRecieved) { Thread.Sleep(100); }.
The problem is if I use 100ms sleep, serial port event handler fire only once and then program stops!(If I debug with breakpoint 100ms works because I create additional time when stepping into the code.) If I wait 4000ms the program works. Also, I check the time between sending data and receiving data from serial port is 200ms. So, 100ms is reasonable.
Here is the code:
public bool dataRecieved = false;
public Form1()
{
InitializeComponent();
}
public void AppendTextBox(string value)
{
this.Invoke((MethodInvoker)delegate { richTextBox1.Text += value + "\n";});
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.Open();
Thread testThread = new Thread(() => sendThread());
testThread.Start();
}
public void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
data = serialPort1.ReadLine();
dataRecieved = true;
}
public void sendThread()
{
for(int i = 0; i<10; i++)
{
serialPort1.WriteLine("AT" + i);
// Following line creates odd situation:
// if Thread.Sleep(100), I receive only first data, then program stops(serial port handler doesnt fire!).
// if Thread.Sleep(4000), I receive all data, successfuly works.
// But I do not want to wait 4000ms, because I receive answer from device in 200ms.
while (!dataRecieved) { Thread.Sleep(100); }
AppendTextBox("Received" + "AT" + i);
dataRecieved = false;
}
}
Where I am wrong? Can you please provide a solution?
I even didn't use a new Thead for write and read on SerialPort. You just need use update control in Invoke() is ok. Below is my update on richTextBox. You can change form richTextBox to your listbox.
public void update_RichTextBox(string message)
{
Invoke(new System.Action(() =>
{
txtReceivedData.Text += message;
txtReceivedData.Refresh();
txtReceivedData.SelectionStart = txtReceivedData.Text.Length;
txtReceivedData.ScrollToCaret();
}));
}
and the way to use above void:
if (ComPort.IsOpen)
{
ComPort.Write(_inputdata + "\r");
Form1._Form1.update_RichTextBox(_inputdata + "\r");
string _receviedData = ComPort.ReadExisting();
Form1._Form1.update_RichTextBox(respond);
ComPort.DiscardInBuffer();//delete all data in device's received buffer
ComPort.DiscardOutBuffer();// delete all data in transmit buffer
}
else
{
MessageBox.Show("haven't yet open COM port");
return "FLASE";
}
I use something I call "Cross Thread Linker"
#region Cross Thread Linker
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate { a(); }));
else return false;
return true;
}
void Update_RichTextBox(RichTextBox rtb, string Text)
{
if (ControlInvokeRequired(rtb, () => Update_RichTextBox(rtb, Text))) return;
rtb.AppendText(Text + Environment.NewLine);
}
#endregion
Then:
Update_RichTextBox(richTextBox1, "Text to append");

C# Background Worker Append TextBox

first off I'd like to say I'm brand new to C# so I am not too aware with how the background worker is supposed to be implemented. I have a GUI program that basically pings a domain a returns the response to a textbox. I am able to get it to work normally, however, it freezes the code because it is running on the same thread which is why I am trying to implement a background worker.
Here is the basic setup
private void button1_Click(object sender, EventArgs e)
{
url = textBox1.Text;
button1.Enabled = false;
button2.Enabled = true;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
do
{
if (bgWorker.CancellationPending)
break;
Invoke((MethodInvoker)delegate { monitor(); });
} while (true);
}
public void monitor()
{
textBox2.AppendText("Status of: " + url + "\n");
Status(url);
System.Threading.Thread.Sleep(30000);
}
private void Status(string url)
{
// This method does all the ping work and also appends the status to the Text box as it goes through , as OK or down
}
I have not worked with bgworkers before and as you can imagine it's confusing. I've looked at tons of other articles and I can't seem to get it. Sorry if the code looks crazy, I'm trying to learn.
Use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq;) and then you can do this:
private void button1_Click(object sender, EventArgs e)
{
var url = textBox1.Text;
Observable
.Interval(TimeSpan.FromMinutes(0.5))
.SelectMany(_ => Observable.Start(() => Status(url)))
.ObserveOn(this)
.Subscribe(status => textBox2.AppendText("Status of: " + status + "\n"));
}
You then just need to change Status to have this signature: string Status(string url).
That's it. No background worker. No invoking. And Status is nicely run on a background thread.
You've got several mistakes. First,
Invoke((MethodInvoker)delegate
{
monitor();
});
will call monitor() on your UI thread. In almost all cases you should not call methods on other threads. You especially should not call methods that block or do anything that takes more than a few milliseconds on your UI thread, and that is what this does:
System.Threading.Thread.Sleep(30000);
Instead of calling a method on another thread; submit immutable data to the other thread and let the thread decide when to handle it. There is an event already built in to BackgroundWorker which does that. Before you call bgWorker.RunWorkerAsync() do this:
url = new Uri(something);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.ProgressChanged += Bgw_ProgressChanged;
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox2.AppendText("Status of: " + url + ": " + e.UserState.ToString()
+ Environment.NewLine);
}
Your bgWorker_DoWork should look more like this:
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgw.CancellationPending)
{
System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
var status = ResultOfPing(e.Argument as Uri);
bgw.ReportProgress(0, status);
}
e.Cancel = true;
}
and you should call it like this:
bgWorker.RunWorkerAsync(url);
You've got a second problem. BackgroundWorker creates a thread, and your thread is going to spend most of its time blocked on a timer or waiting for network responses. That is a poor use of a thread. You would be better off using completion callbacks or async/await.
The background worker is running on a thread pool thread, but your call to Status and Sleep is running on the UI thread. You need to move that stuff back into bgWorker_DoWork.
Try this code:
public partial class Form1 : Form
{
bool cancel;
public Form1()
{
InitializeComponent();
}
public void StartPinging()
{
this.cancel = false;
startButton.Enabled = false;
stopButton.Enabled = true;
responseBox.Clear();
responseBox.AppendText("Starting to ping server.");
responseBox.AppendText(Environment.NewLine);
var bw = new BackgroundWorker
{
WorkerReportsProgress = false,
WorkerSupportsCancellation = true
};
bw.DoWork += (obj, ev) =>
{
while (!cancel)
{
// Ping Server Here
string response = Server.PingServer();
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText(response);
responseBox.AppendText(Environment.NewLine);
}));
}
};
bw.RunWorkerCompleted += (obj, ev) =>
{
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText("Stopped pinging the server.");
responseBox.AppendText(Environment.NewLine);
startButton.Enabled = true;
stopButton.Enabled = false;
}));
};
bw.RunWorkerAsync();
}
delegate void UiMethod();
private void startButton_Click(object sender, EventArgs e)
{
StartPinging();
}
private void stopButton_Click(object sender, EventArgs e)
{
responseBox.AppendText("Cancelation Pressed.");
responseBox.AppendText(Environment.NewLine);
cancel = true;
}
}
public class Server
{
static Random rng = new Random();
public static string PingServer()
{
int time = 1200 + rng.Next(2400);
Thread.Sleep(time);
return $"{time} ms";
}
}
Erwin, when dealing with C# - threads and UI elements usually you will come across cross-thread operations i.e. Background thread with UI threads. This interaction needs to be done in thread safe way with the help of Invoke to avoid invalid operations.
Please look into below resource: InvokeRequired section.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

how to create loading window while web conncetion is checking in windows form app?

I have a test web connection form in c#. I want to show a loading window while my connection is being checked, and then show the result of checking.
This is my code for testing the web connection:
public bool ConnectionAvailable(string strServer)
{
try
{
HttpWebRequest reqFP = (HttpWebRequest)HttpWebRequest.Create(strServer);
HttpWebResponse rspFP = (HttpWebResponse)reqFP.GetResponse();
if (HttpStatusCode.OK == rspFP.StatusCode)
{
// HTTP = 200 - Internet connection available, server online
rspFP.Close();
return true;
}
else
{
// Other status - Server or connection not available
rspFP.Close();
return false;
}
}
catch (WebException)
{
// Exception - connection not available
return false;
}
}
And this:
private void button1_Click(object sender, EventArgs e)
{
string url = "Web-url";
label1.Text = "Checking ...";
button1.Enabled = false;
if (ConnectionAvailable(url))
{
WebClient w = new WebClient();
w.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
label1.Text = w.UploadString(url, "post", "SN=" + textBox1.Text);
button1.Enabled = true;
}
else
{
label1.Text = "Conntion fail";
button1.Enabled = true;
}
}
On a windows forms application the user interface runs on one thread, if you try to run a long running process, which checking the web connection might end up being this will cause the form to freeze until it completes the work.
So, I'd start a new thread that does the check. then raise an event to return the result. while all that's happening you can do what you like with the user interface, such as a loading graphic, or even allow the user to continue using features that don't require the internet connection.
Create EventArgs class of your own so you can pass back the result:
public class ConnectionResultEventArgs : EventArgs
{
public bool Available { get; set; }
}
Then in your form class, create your event, handlers and the method to action when the event arrives
//Create Event and Handler
public delegate void ConnectionResultEventHandler(object sender, ConnectionResultEventArgs e);
public event ConnectionResultEventHandler ConnectionResultEvent;
//Method to run when the event has been receieved, include a delegate in case you try to interact with the UI thread
delegate void ConnectionResultDelegate(object sender, ConnectionResultEventArgs e);
void ConnectionResultReceived(object sender, ConnectionResultEventArgs e)
{
//Check if the request has come from a seperate thread, if so this will raise an exception unless you invoke.
if (InvokeRequired)
{
BeginInvoke(new ConnectionResultDelegate(ConnectionResultReceived), new object[] { this, e });
return;
}
//Do Stuff
if (e.Available)
{
label1.Text = "Connection Good!";
return;
}
label1.Text = "Connection Bad";
}
Subscribe to the event when your form loads:
private void Form1_Load(object sender, EventArgs e)
{
//Subscribe to the the results event.
ConnectionResultEvent += ConnectionResultReceived;
}
and then setup the worker thread:
//Check the connection
void BeginCheck()
{
try
{
HttpWebRequest reqFP = (HttpWebRequest)HttpWebRequest.Create("http://google.co.uk");
HttpWebResponse rspFP = (HttpWebResponse)reqFP.GetResponse();
if (HttpStatusCode.OK == rspFP.StatusCode)
{
// HTTP = 200 - Internet connection available, server online
rspFP.Close();
ConnectionResultEvent(this, new ConnectionResultEventArgs {Available = true});
}
else
{
// Other status - Server or connection not available
rspFP.Close();
ConnectionResultEvent(this, new ConnectionResultEventArgs { Available = false });
}
}
catch (WebException)
{
// Exception - connection not available
//Raise the Event - Connection False
ConnectionResultEvent(this, new ConnectionResultEventArgs { Available = false });
}
}
private void button1_Click(object sender, EventArgs e)
{
//loading graphic, screen or whatever
label1.Text = "Checking Connection...";
//Begin the checks - Start this in a new thread
Thread t = new Thread(BeginCheck);
t.Start();
}
I am thinking of threading! One thread checks the connection while the other one is showing the loading window. If for example the connection has been established you can notify the other thread and show the result.

BackgroundWorker thread is not stopping winforms c#

I am using BackgroundWorker thread to call a small routine which is receiving messages from MSMQ as soon as the message comes to MSMQ queue(messages comes to msmq in every 5 seconds) it comes to my application which is using BackgroundWorker thread. Below is my Win Form class. I am new to threading so please appology if I am doing something wrong
Problem: My application is MDI application, when I am executing my application first time it works perfectly fine and receives the MSMQ message as soon as it comes to the queue, which is every 5 seconds but when ever I close this form which is a child form it closes fine, but right after opening this same form I am receiving messages from MSMQ with 10 seconds of dalay, so it means I am messing up something in the background worker thread, I tried to cancel this background worker thread but I am failed and unable to properly cancle or terminate the thread. Please help and share your experience. below is my form code.
public partial class FrmBooking : BookingManager.Core.BaseForm.BaseForm
{
internal const string queName = #"messageServer\private$\Response";
private Int32 counter = 0;
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
public FrmBooking()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.RunWorkerCompleted+=new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged+=new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.DoWork+=new DoWorkEventHandler(backgroundWorker1_DoWork);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgWorker = sender as BackgroundWorker;
if (bgWorker.CancellationPending)
{
e.Cancel = true;
return;
}
try
{
MessageQueue messageQueue = null;
if (MessageQueue.Exists(queName))
{
messageQueue = new MessageQueue(queName);
}
else
{
MessageQueue.Create(queName);
}
messageQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
System.Messaging.Message msg = messageQueue.Receive();
bgWorker.ReportProgress(100, msg);
}
catch (Exception ex) { }
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
System.Messaging.Message msg = e.UserState as System.Messaging.Message;
listBoxControl1.Items.Add(msg.Body.ToString());
counter++;
labelControl1.Text = String.Format("Total messages received {0}", counter.ToString());
}
private void FrmBooking_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
}
You have two options here:
1) In the Form's Closing event, call backgroundWorker1.CancelAsync().
2) A better approach would be to remove your background worker altogether and use the the MessageQueue's native asynchronous processing mechanism for this. You can start the queue request using the BeginReceive method after adding a ReceivedCompleted event handler, then in the completed event handler, process the message and restart the request.
The issue is that if you issue a Receive request, it will block the background worker thread until a message is received in the queue, and CancelAsync will only request that the background worker be stopped, it won't cancel the Receive request.
For example (updated):
public partial class FrmBooking : BookingManager.Core.BaseForm.BaseForm
{
public FrmBooking()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(FrmBooking_FormClosing);
}
internal const string queName = #"messageServer\private$\Response";
private Int32 counter = 0;
private MessageQueue messageQueue = null;
private bool formIsClosed = false;
private void FrmBooking_Load(object sender, EventArgs e)
{
StartQueue();
}
void FrmBooking_FormClosing(object sender, FormClosingEventArgs e)
{
// Set the flag to indicate the form is closed
formIsClosed = true;
// If the messagequeue exists, close it
if (messageQueue != null)
{
messageQueue.Close();
}
}
private void StartQueue()
{
if (MessageQueue.Exists(queName))
{
messageQueue = new MessageQueue(queName);
}
else
{
MessageQueue.Create(queName);
}
messageQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
// Add an event handler for the ReceiveCompleted event.
messageQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(MessageReceived);
messageQueue.BeginReceive(TimeSpan.FromSeconds(15));
}
// Provides an event handler for the ReceiveCompleted event.
private void MessageReceived(Object source, ReceiveCompletedEventArgs asyncResult)
{
if (!this.formIsClosed)
{
// End the asynchronous receive operation.
System.Messaging.Message msg = messageQueue.EndReceive(asyncResult.AsyncResult);
// Display the message information on the screen.
listBoxControl1.Items.Add(msg.Body.ToString());
counter++;
labelControl1.Text = String.Format("Total messages received {0}", counter.ToString());
// Start receiving the next message
messageQueue.BeginReceive(TimeSpan.FromSeconds(15));
}
}
}
When you close the form, do you terminate the worker? If not, if there is a reference to the form or to the worker somewhere (event handlers?) then it will not get GCd and stays around. Upon opening the second instance you may have two background workers running....

How to wait for a BackgroundWorker to cancel?

Consider a hypothetical method of an object that does stuff for you:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
How can one wait for a BackgroundWorker to be done?
In the past people have tried:
while (_worker.IsBusy)
{
Sleep(100);
}
But this deadlocks, because IsBusy is not cleared until after the RunWorkerCompleted event is handled, and that event can't get handled until the application goes idle. The application won't go idle until the worker is done. (Plus, it's a busy loop - disgusting.)
Others have add suggested kludging it into:
while (_worker.IsBusy)
{
Application.DoEvents();
}
The problem with that is that is Application.DoEvents() causes messages currently in the queue to be processed, which cause re-entrancy problems (.NET isn't re-entrant).
I would hope to use some solution involving Event synchronization objects, where the code waits for an event - that the worker's RunWorkerCompleted event handlers sets. Something like:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
But I'm back to the deadlock: the event handler can't run until the application goes idle, and the application won't go idle because it's waiting for an Event.
So how can you wait for an BackgroundWorker to finish?
Update
People seem to be confused by this question. They seem to think that I will be using the BackgroundWorker as:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
That is not it, that is not what I'm doing, and that is not what is being asked here. If that were the case, there would be no point in using a background worker.
If I understand your requirement right, you could do something like this (code not tested, but shows the general idea):
private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
}
public void Cancel()
{
worker.CancelAsync();
_resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
while(!e.Cancel)
{
// do something
}
_resetEvent.Set(); // signal that worker is done
}
There is a problem with this response. The UI needs to continue to process messages while you are waiting, otherwise it will not repaint, which will be a problem if your background worker takes a long time to respond to the cancel request.
A second flaw is that _resetEvent.Set() will never be called if the worker thread throws an exception - leaving the main thread waiting indefinitely - however this flaw could easily be fixed with a try/finally block.
One way to do this is to display a modal dialog which has a timer that repeatedly checks if the background worker has finished work (or finished cancelling in your case). Once the background worker has finished, the modal dialog returns control to your application. The user can't interact with the UI until this happens.
Another method (assuming you have a maximum of one modeless window open) is to set ActiveForm.Enabled = false, then loop on Application,DoEvents until the background worker has finished cancelling, after which you can set ActiveForm.Enabled = true again.
Almost all of you are confused by the question, and are not understanding how a worker is used.
Consider a RunWorkerComplete event handler:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
rocketOnPad = false;
label1.Text = "Rocket launch complete.";
}
else
{
rocketOnPad = true;
label1.Text = "Rocket launch aborted.";
}
worker = null;
}
And all is good.
Now comes a situation where the caller needs to abort the countdown because they need to execute an emergency self-destruct of the rocket.
private void BlowUpRocket()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
StartClaxon();
SelfDestruct();
}
And there is also a situation where we need to open the access gates to the rocket, but not while doing a countdown:
private void OpenAccessGates()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
if (!rocketOnPad)
DisengageAllGateLatches();
}
And finally, we need to de-fuel the rocket, but that's not allowed during a countdown:
private void DrainRocket()
{
if (worker != null)
{
worker.CancelAsync();
WaitForWorkerToFinish(worker);
worker = null;
}
if (rocketOnPad)
OpenFuelValves();
}
Without the ability to wait for a worker to cancel, we must move all three methods to the RunWorkerCompletedEvent:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
rocketOnPad = false;
label1.Text = "Rocket launch complete.";
}
else
{
rocketOnPad = true;
label1.Text = "Rocket launch aborted.";
}
worker = null;
if (delayedBlowUpRocket)
BlowUpRocket();
else if (delayedOpenAccessGates)
OpenAccessGates();
else if (delayedDrainRocket)
DrainRocket();
}
private void BlowUpRocket()
{
if (worker != null)
{
delayedBlowUpRocket = true;
worker.CancelAsync();
return;
}
StartClaxon();
SelfDestruct();
}
private void OpenAccessGates()
{
if (worker != null)
{
delayedOpenAccessGates = true;
worker.CancelAsync();
return;
}
if (!rocketOnPad)
DisengageAllGateLatches();
}
private void DrainRocket()
{
if (worker != null)
{
delayedDrainRocket = true;
worker.CancelAsync();
return;
}
if (rocketOnPad)
OpenFuelValves();
}
Now I could write my code like that, but I'm just not gonna. I don't care, I'm just not.
You can check into the RunWorkerCompletedEventArgs in the RunWorkerCompletedEventHandler to see what the status was. Success, canceled or an error.
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
Console.WriteLine("The worker was cancelled.");
}
}
Update: To see if your worker has called .CancelAsync() by using this:
if (_worker.CancellationPending)
{
Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}
You don't wait for the background worker to complete. That pretty much defeats the purpose of launching a separate thread. Instead, you should let your method finish, and move any code that depends on completion to a different place. You let the worker tell you when it's done and call any remaining code then.
If you want to wait for something to complete use a different threading construct that provides a WaitHandle.
Why can't you just tie into the BackgroundWorker.RunWorkerCompleted Event. It's a callback that will "Occur when the background operation has completed, has been canceled, or has raised an exception."
I don't understand why you'd want to wait for a BackgroundWorker to complete; it really seems like the exact opposite of the motivation for the class.
However, you could start every method with a call to worker.IsBusy and have them exit if it is running.
Hm maybe I am not getting your question right.
The backgroundworker calls the WorkerCompleted event once his 'workermethod' (the method/function/sub that handles the backgroundworker.doWork-event) is finished so there is no need for checking if the BW is still running.
If you want to stop your worker check the cancellation pending property inside your 'worker method'.
The workflow of a BackgroundWorker object basically requires you to handle the RunWorkerCompleted event for both normal execution and user cancellation use cases. This is why the property RunWorkerCompletedEventArgs.Cancelled exists. Basically, doing this properly requires that you consider your Cancel method to be an asynchronous method in itself.
Here's an example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
namespace WindowsFormsApplication1
{
public class AsyncForm : Form
{
private Button _startButton;
private Label _statusLabel;
private Button _stopButton;
private MyWorker _worker;
public AsyncForm()
{
var layoutPanel = new TableLayoutPanel();
layoutPanel.Dock = DockStyle.Fill;
layoutPanel.ColumnStyles.Add(new ColumnStyle());
layoutPanel.ColumnStyles.Add(new ColumnStyle());
layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
_statusLabel = new Label();
_statusLabel.Text = "Idle.";
layoutPanel.Controls.Add(_statusLabel, 0, 0);
_startButton = new Button();
_startButton.Text = "Start";
_startButton.Click += HandleStartButton;
layoutPanel.Controls.Add(_startButton, 0, 1);
_stopButton = new Button();
_stopButton.Enabled = false;
_stopButton.Text = "Stop";
_stopButton.Click += HandleStopButton;
layoutPanel.Controls.Add(_stopButton, 1, 1);
this.Controls.Add(layoutPanel);
}
private void HandleStartButton(object sender, EventArgs e)
{
_stopButton.Enabled = true;
_startButton.Enabled = false;
_worker = new MyWorker() { WorkerSupportsCancellation = true };
_worker.RunWorkerCompleted += HandleWorkerCompleted;
_worker.RunWorkerAsync();
_statusLabel.Text = "Running...";
}
private void HandleStopButton(object sender, EventArgs e)
{
_worker.CancelAsync();
_statusLabel.Text = "Cancelling...";
}
private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
_statusLabel.Text = "Cancelled!";
}
else
{
_statusLabel.Text = "Completed.";
}
_stopButton.Enabled = false;
_startButton.Enabled = true;
}
}
public class MyWorker : BackgroundWorker
{
protected override void OnDoWork(DoWorkEventArgs e)
{
base.OnDoWork(e);
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(500);
if (this.CancellationPending)
{
e.Cancel = true;
e.Result = false;
return;
}
}
e.Result = true;
}
}
}
If you really really don't want your method to exit, I'd suggest putting a flag like an AutoResetEvent on a derived BackgroundWorker, then override OnRunWorkerCompleted to set the flag. It's still kind of kludgy though; I'd recommend treating the cancel event like an asynchronous method and do whatever it's currently doing in the RunWorkerCompleted handler.
I'm a little late to the party here (about 4 years) but what about setting up an asynchronous thread that can handle a busy loop without locking the UI, then have the callback from that thread be the confirmation that the BackgroundWorker has finished cancelling?
Something like this:
class Test : Form
{
private BackgroundWorker MyWorker = new BackgroundWorker();
public Test() {
MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork);
}
void MyWorker_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < 100; i++) {
//Do stuff here
System.Threading.Thread.Sleep((new Random()).Next(0, 1000)); //WARN: Artificial latency here
if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled
}
}
public void CancelWorker() {
if (MyWorker != null && MyWorker.IsBusy) {
MyWorker.CancelAsync();
System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() {
while (MyWorker.IsBusy) {
System.Threading.Thread.Sleep(100);
}
});
WaitThread.BeginInvoke(a => {
Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread
StuffAfterCancellation();
});
}, null);
} else {
StuffAfterCancellation();
}
}
private void StuffAfterCancellation() {
//Things to do after MyWorker is cancelled
}
}
In essence what this does is fire off another thread to run in the background that just waits in it's busy loop to see if the MyWorker has completed. Once MyWorker has finished cancelling the thread will exit and we can use it's AsyncCallback to execute whatever method we need to follow the successful cancellation - it'll work like a psuedo-event. Since this is separate from the UI thread it will not lock the UI while we wait for MyWorker to finish cancelling. If your intention really is to lock and wait for the cancel then this is useless to you, but if you just want to wait so you can start another process then this works nicely.
I know this is really late (5 years) but what you are looking for is to use a Thread and a SynchronizationContext. You are going to have to marshal UI calls back to the UI thread "by hand" rather than let the Framework do it auto-magically.
This allows you to use a Thread that you can Wait for if needs be.
Imports System.Net
Imports System.IO
Imports System.Text
Public Class Form1
Dim f As New Windows.Forms.Form
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
Dim l As New Label
l.Text = "Please Wait"
f.Controls.Add(l)
l.Dock = DockStyle.Fill
f.StartPosition = FormStartPosition.CenterScreen
f.FormBorderStyle = Windows.Forms.FormBorderStyle.None
While BackgroundWorker1.IsBusy
f.ShowDialog()
End While
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim i As Integer
For i = 1 To 5
Threading.Thread.Sleep(5000)
BackgroundWorker1.ReportProgress((i / 5) * 100)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Text = e.ProgressPercentage
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
f.Close()
End Sub
End Class
Fredrik Kalseth's solution to this problem is the best I've found so far. Other solutions use Application.DoEvent() that can cause problems or simply don't work. Let me cast his solution into a reusable class. Since BackgroundWorker is not sealed, we can derive our class from it:
public class BackgroundWorkerEx : BackgroundWorker
{
private AutoResetEvent _resetEvent = new AutoResetEvent(false);
private bool _resetting, _started;
private object _lockObject = new object();
public void CancelSync()
{
bool doReset = false;
lock (_lockObject) {
if (_started && !_resetting) {
_resetting = true;
doReset = true;
}
}
if (doReset) {
CancelAsync();
_resetEvent.WaitOne();
lock (_lockObject) {
_started = false;
_resetting = false;
}
}
}
protected override void OnDoWork(DoWorkEventArgs e)
{
lock (_lockObject) {
_resetting = false;
_started = true;
_resetEvent.Reset();
}
try {
base.OnDoWork(e);
} finally {
_resetEvent.Set();
}
}
}
With flags and proper locking, we make sure that _resetEvent.WaitOne() really gets only called if some work has been started, otherwise _resetEvent.Set(); might never been called!
The try-finally ensures that _resetEvent.Set(); will be called, even if an exception should occur in our DoWork-handler. Otherwise the application could freeze forever when calling CancelSync!
We would use it like this:
BackgroundWorkerEx _worker;
void StartWork()
{
StopWork();
_worker = new BackgroundWorkerEx {
WorkerSupportsCancellation = true,
WorkerReportsProgress = true
};
_worker.DoWork += Worker_DoWork;
_worker.ProgressChanged += Worker_ProgressChanged;
}
void StopWork()
{
if (_worker != null) {
_worker.CancelSync(); // Use our new method.
}
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 20; i++) {
if (worker.CancellationPending) {
e.Cancel = true;
break;
} else {
// Simulate a time consuming operation.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(5 * i);
}
}
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressLabel.Text = e.ProgressPercentage.ToString() + "%";
}
You can also add a handler to the RunWorkerCompleted event as shown here:
BackgroundWorker Class (Microsoft documentation).
Just wanna say I came here because I need a background worker to wait while I was running an async process while in a loop, my fix was way easier than all this other stuff^^
foreach(DataRow rw in dt.Rows)
{
//loop code
while(!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
Just figured I'd share because this is where I ended up while searching for a solution. Also, this is my first post on stack overflow so if its bad or anything I'd love critics! :)
Closing the form closes my open logfile. My background worker writes that logfile, so I can't let MainWin_FormClosing() finish until my background worker terminates. If I don't wait for my background worker to terminate, exceptions happen.
Why is this so hard?
A simple Thread.Sleep(1500) works, but it delays shutdown (if too long), or causes exceptions (if too short).
To shut down right after the background worker terminates, just use a variable. This is working for me:
private volatile bool bwRunning = false;
...
private void MainWin_FormClosing(Object sender, FormClosingEventArgs e)
{
... // Clean house as-needed.
bwInstance.CancelAsync(); // Flag background worker to stop.
while (bwRunning)
Thread.Sleep(100); // Wait for background worker to stop.
} // (The form really gets closed now.)
...
private void bwBody(object sender, DoWorkEventArgs e)
{
bwRunning = true;
BackgroundWorker bw = sender as BackgroundWorker;
... // Set up (open logfile, etc.)
for (; ; ) // infinite loop
{
...
if (bw.CancellationPending) break;
...
}
... // Tear down (close logfile, etc.)
bwRunning = false;
} // (bwInstance dies now.)
You can piggy back off of the RunWorkerCompleted event. Even if you've already added an event handler for _worker, you can add another an they will execute in the order in which they were added.
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) =>
{
// do whatever you want to do when the cancel completes in here!
});
_worker.CancelAsync();
}
}
this could be useful if you have multiple reasons why a cancel may occur, making the logic of a single RunWorkerCompleted handler more complicated than you want. For instance, cancelling when a user tries to close the form:
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_worker != null)
{
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => this.Close());
_worker.CancelAsync();
e.Cancel = true;
}
}
I use async method and await to wait for the worker finishing its job:
public async Task StopAsync()
{
_worker.CancelAsync();
while (_isBusy)
await Task.Delay(1);
}
and in DoWork method:
public async Task DoWork()
{
_isBusy = true;
while (!_worker.CancellationPending)
{
// Do something.
}
_isBusy = false;
}
You may also encapsulate the while loop in DoWork with try ... catch to set _isBusy is false on exception. Or, simply check _worker.IsBusy in the StopAsync while loop.
Here is an example of full implementation:
class MyBackgroundWorker
{
private BackgroundWorker _worker;
private bool _isBusy;
public void Start()
{
if (_isBusy)
throw new InvalidOperationException("Cannot start as a background worker is already running.");
InitialiseWorker();
_worker.RunWorkerAsync();
}
public async Task StopAsync()
{
if (!_isBusy)
throw new InvalidOperationException("Cannot stop as there is no running background worker.");
_worker.CancelAsync();
while (_isBusy)
await Task.Delay(1);
_worker.Dispose();
}
private void InitialiseWorker()
{
_worker = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
_worker.DoWork += WorkerDoWork;
}
private void WorkerDoWork(object sender, DoWorkEventArgs e)
{
_isBusy = true;
try
{
while (!_worker.CancellationPending)
{
// Do something.
}
}
catch
{
_isBusy = false;
throw;
}
_isBusy = false;
}
}
To stop the worker and wait for it runs to the end:
await myBackgroundWorker.StopAsync();
The problems with this method are:
You have to use async methods all the way.
await Task.Delay is inaccurate. On my PC, Task.Delay(1) actually waits ~20ms.
oh man, some of these have gotten ridiculously complex. all you need to do is check the BackgroundWorker.CancellationPending property inside the DoWork handler. you can check it at any time. once it's pending, set e.Cancel = True and bail from the method.
// method here
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (sender as BackgroundWorker);
// do stuff
if(bw.CancellationPending)
{
e.Cancel = True;
return;
}
// do other stuff
}

Categories

Resources