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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With