开发者

How do you set the UserState in the RunWorkerCompletedEventArgs object?

开发者 https://www.devze.com 2023-01-27 07:22 出处:网络
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).

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 开发者_Go百科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
}
0

精彩评论

暂无评论...
验证码 换一张
取 消