Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 968
sisdog Avatar asked Nov 30 '10 03:11

sisdog


3 Answers

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()) 
                    });
    }
}
like image 75
Philip Rieck Avatar answered Oct 18 '22 09:10

Philip Rieck


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).

like image 4
lesscode Avatar answered Oct 18 '22 09:10

lesscode


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
}
like image 1
Kit Avatar answered Oct 18 '22 09:10

Kit