Passing results back to main thread from a cancelled BackgroundWorker thread - c#

How can I cancel a backgroundworker and pass back an error message. I know that you can use DoWorkEventArgs e.Results to pass back results to main thread but e.Results gets overwritten when I cancel the child thread. Example:
private MyProgram_DoWork(object sender, DoWorkEventArgs e)
{
e.Cancel = true;
e.Result = "my error message";
return;
}
private void MyProgram_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
string ErrorMsg = (string)e.Result; //exception happens here
....
}
else
{
// success code
}
}
Is there another way to stop my child thread and send a string back to the main thread?

If your long-running process was canceled, it wouldn't really have a "result", as the process didn't completely finish.
According to the documentation:
Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.
I took a peek inside BackgroundWorker. Here's the contents of the Result property:
public object Result
{
get
{
this.RaiseExceptionIfNecessary();
return this.result;
}
}
And the contents of RaiseExceptionIfNecessary():
protected void RaiseExceptionIfNecessary()
{
if (this.Error != null)
throw new TargetInvocationException(SR.GetString("Async_ExceptionOccurred"), this.Error);
if (this.Cancelled)
throw new InvalidOperationException(SR.GetString("Async_OperationCancelled"));
}
So if you cancel the thread, referencing Result will throw an InvalidOperationException. That's just the way it's designed.
I don't know what the "best" way is to pass a string back. I'd say you could define a variable in the same method you run the BackgroundWorker from, and assign a value to it from the DoWork event.
You just have to be very careful that nothing on the UI thread is somehow bound to the variable or you could run into problems. A string should be safe, but don't start adding to a list that's bound to a ComboBox or something.

Related

Confused about backgroundworker not stopping when expected

I have the following code. It is just a form app. On load it will run the bacground worker.
Then I have a button that is supposed to stop the infinite loop in the background worker by setting a flag to true.
I'm logging the out put of the backgroundworker1.IsBusy and it says it is busy but according to the logic in my code it shouldn't be busy because I set the flag to true thus exiting the while loop and running the backgroundworker_Completed event.
I must be doing something wrong but I can not figure it out.
If I'm approaching this incorrectly could somebody either help me fix what I'm doing wrong or point me in a better direction on how I can accomplish what I"m trying to do here.
private volatile bool StopScanning = false;
private void myForm_Load(object sender, EventArgs e)
{
try
{
if (backgroundWorker1.IsBusy)
{
//do nothing
}
else
{
backgroundWorker1.RunWorkerAsync();
}
}
catch (Exception boo)
{
Log.log(boo.ToString());
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (StopScanning == false)
{
Application.DoEvents();
try
{
ReturnScannedItems();
System.Threading.Thread.Sleep(1000);
}
catch (Exception boo)
{
Log.log(boo.ToString());
}
}
}
private void cancelbutton_Click(object sender, EventArgs e)
{
try
{
Log.log("Setting Stop Scan flag to true");
StopScanning = true;
Log.log(CloseScanSession().ToString());
}
catch (Exception boo)
{
Log.log("Setting Stop Scan flag to true");
StopScanning = true;
Log.log(CloseScanSession().ToString());
Log.log(boo.ToString());
}
while (backgroundWorker1.IsBusy)
{
Log.log("Still busy");
}
this.Close();
}
You are blocking the UI thread, which prevents the BackgroundWorker from completing. It can't raise the RunWorkerCompleted event until the UI thread is free to process new messages (raising the event involves posting a message to the UI thread's message queue, so that the UI thread can then execute the code that will actually raise the event).
Your code also is flawed in that it's calling Application.DoEvents() from the worker thread. You should never call this method anyway, but it's particularly foolish to call it from a worker thread, because the whole point of having a worker thread is to avoid having to call that method (and it won't do anything when called on the worker thread anyway, because the worker thread shouldn't own any window objects that would need to receive a window message).
Instead of sitting in a busy loop, checking IsBusy and blocking the UI thread, you should just subscribe to the RunWorkerCompleted event and do whatever you need to do there. Without a good Minimal, Complete, and Verifiable code example that fully illustrates what you're actually trying to do, it's not possible to provide any more specific advice than that.

how to use return method in backgroundworker

i use background worker to call method but in return give me an Exception here is the code
private void Button_Click(object sender, RoutedEventArgs e)
{
BGW.RunWorkerAsync();
}
void BGW_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = new Drug_Class().Search(Filter);
}
class Drug_Class
{
public List<WorkSpaceVariable.Drug_List> Search(Expression<Func<db.Drug_Catalog, bool>> Filter)
{
using (db.PVDBDataContext PVDB=new PVDBDataContext ())
{
try
{
var DQuary = from D in PVDB.Drug_Catalogs.Where(Filter)
select new WorkSpaceVariable.Drug_List
{
Drugs_ID = D.Drugs_ID
}
return DQuary.ToList() ;//Exception The calling thread cannot access this object because a different thread owns it.
}
catch (Exception E)
{
return null;
}
}
}
i don't understand why i got this Exception may any one tell me whats wrong with my code ?
The Exception that you got really explains your problem... all you need to do is to read it:
The calling thread cannot access this object because a different thread owns it.
You should hopefully know that a BackgroundWorker works on a background thread, so you should be able to work out that the two threads mentioned would be this background thread and the main UI thread.
What it means is that you cannot manipulate UI objects that were declared on (and therefore owned by) the main UI thread on any other thread.
The solution is to not manipulate UI objects from the UI thread in your background thread. Instead, just manipulate the data in the background thread and when the BackgroundWorker has finished, then update the UI element by updating the data bound collection.

ManualResetEvent with a BackgroundWorker : Current thread is busy while WaitOne()?

Imagine following situation:
I got a signal on the ui thread from a third party server.
I start a BackgroundWorker with RunAsync to fetch data from a database and another async thread, which shall poll
another hardware and receive signals, also not in ui thread
Inside the bg's DoWork eventhandler I call manualresetEvent.Reset(). Then I call the data-fetching method, and then I call manualresetEvent.Set() and in the end I call the method METH_UI_1 on the ui thread by invoking it.
The other hardware thread shall receive hardware-data, which then itself is passed via Invoke to the ui into the ui thread to set some ui-elements periodically depending on the hardware-data I get.
The data from database can also not be fetched yet, but the ui must react to the hardware-data, which is polled by the second async thread.
In METH_UI_1 I call manualresetEvent.WaitOne();
Some times I get the exception, that the background worker is busy and cannot run multiple tasks concurrently.
a) Is there really a need for a ManualResetEvent object ?
b) Would it be enough, to check for the isBusy property in order to issue WaitOne() only, when the background worker is no more busy ?
UPDATE: CODE.
MainForm.cs (event handler of third party hw-vendor, component, handled in ui thread)
private void thrdptyPlcGotData(object sender, thrdptyPlcGotDataEventArgs e)
{
string strError = string.Empty;
bool blNotReadyYet = false;
try
{
ThrdPtyPlcIfs.DataSetthrdptyPlc ds;
ds = new ThrdPtyPlcIfs.Dataset();
e.FillDataToTDataSet(ds);
ThrdPtyPlcIfs.Statics.SaveDataSet(ds, CLStatics.FileName);
if (this.ValidateDsDetail(ds))
{
// begin async work..... ask db, continue asking scale-> inside got weight of scale the rest is handled ( using or trashing db data )
this.ExtractDataOfDataSet(ds);
this.bgWorkerStart_Get_Data.RunWorkerAsync();
_oAsyncScaleManager.StartThread();
}
}
}
runworkerasynch does this:
private void bgWorkerStart_Get_Data_RFC_DoWork(object sender, DoWorkEventArgs e)
{
try
{
_blnStart_Get_Data_RFC = this.StartGetData_RFC(null);
}
catch (Exception ex)
{
LogExcep(ex);
_blnStart_Get_Data_RFC = false;
}
}
WorkCompleted EventHandler of the BackGroundWorker:
private void bgWorkerStart_Get_Data_RFC_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
if (InvokeRequired)
{
this.Invoke((MethodInvoker)delegate()
{
this.ApplyDbDataToUi();
}
);
}
else
{
this.ApplyDbDataToUi();
}
}
catch (Exception ex)
{
LogAndShowExep(ex);
}
}
As rare as it might be , its possible the BackgrounWorker isn't finished when you set manualresetEvent inside the dowork method block. If its at the very end , I would hook into the backgroundworker workcompleted event and set it in there.

This BackgroundWorker is currently busy and cannot run multiple tasks concurrently

I'm trying to use a Background Worker in a WPF application. The heavy lifting task uses WebClient to download some HTML and parse some info out of it. Ideally I want to do that downloading and parsing without locking the UI and placing the results in the UI once it's done working.
And it works fine, however, if I quickly submit the "download and parse" command, I get the error:
This BackgroundWorker is currently busy and cannot run multiple tasks
concurrently
So I did some Googling and it seems that I can enable the .WorkerSupportsCancellation property of the background worker and just .CancelAsync(). However, this doesn't work as expected (canceling the current download and parse).
I still get the above error.
Here's my code:
//In window constructor.
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
//Declared at class level variable.
BackgroundWorker _backgroundWorker = new BackgroundWorker();
//This is the method I call from my UI.
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
_backgroundWorker.CancelAsync();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
POCOClassFoo foo = new POCOClassFoo();
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This automagically sets the UI to the data.
Foo.DataContext = foo;
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
foo = parseanddownloadresult()!
}
Calling CancelAsync will still fire the RunWorkerCompleted event. In this event, you need to make sure that CancelAsync has not been called, by checking e.Cancelled. Until this event fires, you cannot call RunWorkerAsync.
Alternatively, I would recommend you do what Tigran suggested and create a new BackgroundWorker each time.
Further more, I would recommend storing the results of_backgroundWorker_DoWork in e.Result, then retrieve them from the same in _backgroundWorker_RunWorkerCompleted
Maybe something like this
BackgroundWorker _backgroundWorker;
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.RunWorkerCompleted += new _backgroundWorker_RunWorkerCompleted;
return bw.
}
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
if (_backgroundWorer != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
//you no longer need this because the value is being stored in e.Result
//POCOClassFoo foo = new POCOClassFoo();
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Error handling goes here.
}
else
{
if (e.Cancelled)
{
//handle cancels here.
}
{
//This automagically sets the UI to the data.
Foo.DataContext = (POCOClassFoo)e.Result;
}
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
e.Result = parseanddownloadresult()!
}
The thing is that CancelAsync() does what it climes: cancel in async way. That means that it will not stop immediately, but after some time. That time can never be calculated or predicted, so you have a couple of options:
Wait until this backround worker stops really, by waiting in cycle until IsBusy property of it becomes false
Or, I think, better solution is to start another background worker, considering that request of cancelation was already sent to the first one, so it will be soon or later stop. In this case, you need to know from which background worker data comes, in order to process it or not, cause on start of second the first one will still run and pump the data from WebService.
Hope this helps.
CancelAsync returns before the worker cancels and stops its work. Hence, your RunWorkerAsync call is starting before the worker is ready, and you're getting that error. You'll need to wait for the worker to be ready first.
When I'm not interested in tracking progress of an async operation, I tend to prefer to just slap a lambda at ThreadPool.QueueUserWorkItem instead of instantiating and setting up a background worker that I have to check the state of to be able to reuse in a sane way.
You need to verify before you kicks in.
f( !bw.IsBusy )
bw.RunWorkerAsync();
else
MessageBox.Show("Can't run the bw twice!");
You are calling CancelAsync without waiting for the background worker to actually cancel the work. Also you must have your own logic for cancelling the work. There is a good example on MSDN which shows how to do it. Basically in your parseanddownloadresult() method you need to check the CancellationPending property.

How do you set the UserState in the RunWorkerCompletedEventArgs object?

HI all.
I have an array of BackgroundWorker objects running instances of a Worker class. When I call the Worker class the object instance does it's thing and then runs out of code (the loop finishes). I'm able to listen to the RunWorkerCompleted() event but when it calls the delegate that I've set up I need to know which of my Worker objects just completed.
I see a UserState property in the RunWorkerCompletedEventArgs that comes to my delegate but I have no idea how to set this in my Worker object as it's finishing.
Any ideas?
snippet from my WorkManager.cs class
public Worker AddWorker()
{
Worker w = new Worker();
_workers.Add(w.WorkerID,w);
BackgroundWorker bg = new BackgroundWorker();
_bgworkers.Add(bg);
bg.DoWork += w.Start;
bg.WorkerReportsProgress = true;
bg.WorkerSupportsCancellation = true;
bg.ProgressChanged += ProcessWorkerMessage;
bg.RunWorkerCompleted += WorkerFinished;
w.WorkManager = this;
w.BackgroundWorker = bg;
bg.RunWorkerAsync(w);
return w;
}
public void WorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
if (_onManagerEvent != null)
_onManagerEvent(new ManagerEvent { EventDate = DateTime.Now, Message = "Worker ??? successfully ended." });
}
So when my Worker object finishes the loop in its Start() method, what do I do to fill the userState property of the RunWorkerCompleteEventArgs object "e" that is passed to my WorkerFinished method()?
Thanks
Your Start method on the Worker class can set the Result property of the DoWorkEventArgs argument. Here's an example:
void Start(object sender, DoWorkEventArgs e)
{
//Do your loop and other work.
e.Result = this;
}
Then in the finish event handler, you can retrieve e.Result:
public void WorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
//You should always check e.Cancelled and e.Error before checking e.Result!
// ... even though I'm skipping that here
Worker w = e.Result as Worker;
if( w != null)
{
if (_onManagerEvent != null)
_onManagerEvent(new ManagerEvent
{
EventDate = DateTime.Now,
Message = String.Format("Worker {0} successfully ended."
, w.ToString())
});
}
}
That UserState thing is a known bug in BackgroundWorker:
http://www.pluralsight-training.net/community/blogs/mike/archive/2005/10/21/15783.aspx (archive.org link…original link is dead)
What I've done in the past when I've been in your situation is either use RunWorkerCompletedEventArgs.Result (as Philip suggests), or, if possible, have my worker derive from BackgroundWorker (then I can add as much extra state as I want, and get the whole worker as the sender argument to the events raised by BackgroundWorker, while still being able to use Result for its intended purpose).
Fifteen years later the bug mentioned in lesscode's answer has not been fixed, even after Microsoft ported Winforms to .NET Core. I had the additional requirement that I needed to get the "user object" when the worker was canceled.
Because you can't set that user object and you can't access the result when the worker is canceled, I had to keep track of cancellation separately from the RunWorkerCompletedEventArgs implementation.
Here is my solution. First, create a result object that does double duty (sorry SRP) as a user object—something like
class WorkerStateAndResult
{
public bool Errored { get; set; }
public bool Canceled { get; set; }
// other state/results...
}
Then, in your worker's handler, immediately set the Result property to an instance of WorkerStateAndResult. Inside your handler, you will not set the DoWorkEventArgs.Canceled to true when the worker is canceled; you will instead set the property on your state object; the same is true for error cases. Your handler ends up looking something like
private void HandleWorkerDoWork(object sender, DoWorkEventArgs e)
{
var stateAndResult = new WorkerStateAndResult(...);
e.Result = stateAndResult;
try
{
// do the work and check for cancellation. if cancellation happens, set Canceled
// instead of using built-in e.Canceled property
stateAndResult.Canceled = ResultOfActualWork();
}
catch
{
// handle errors, again, not using built-in mechanism
stateAndResult.Error = true;
}
finally
{
// any cleanup
}
}
Finally, in the RunWorkerCompleted handler, you can access result, which has all of your results and state, and you can check the Error and Canceled properties to do whatever logic is needed:
private void HandleAnalyzerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// this would normally throw if error or canceled, but not anymore!
var result = (WorkerStateAndResult)e.Result;
if (result.Canceled)
{
// do canceled logic
}
else if (result.Errored)
{
// do error logic
}
else
// do regular logic using result
}

Categories