62

I need to make RunWorkerAsync() return a List<FileInfo>.

What is the process to be able to return an object from a background worker?

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122

8 Answers8

102

In your DoWork event handler for the BackgroundWorker (which is where the background work takes place) there is an argument DoWorkEventArgs. This object has a public property object Result. When your worker has generated its result (in your case, a List<FileInfo>), set e.Result to that, and return.

Now that your BackgroundWorker has completed its task, it triggers the RunWorkerCompleted event, which has a RunWorkerCompletedEventArgs object as an argument. RunWorkerCompletedEventArgs.Result will contain the result from your BackgroundWorker.

example:

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    int result = 2+2;
    e.Result = result;
}

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    int result = (int)e.Result;
    MessageBox.Show("Result received: " + result.ToString());
}
David
  • 3,177
  • 1
  • 18
  • 15
  • And how about if I want to access the `e.Result` object outside of the RunWorkerCompleted method? Just assign it to a global variable? Seems a bit ugly... – Dan W Mar 07 '22 at 13:07
26

I'm assuming that you don't want to block and wait on RunWorkerAsync() for the results (if you did, there would be no reason to run async!

If you want to be notified when the background process finishes, hook the RunWorkerCompleted Event. If you want to return some state, return it in the Result member of DoWork's event args.

Example:



    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
      // do your thing
      ....
      // return results
      e.Result = theResultObject;
    }
    
    // now get your results
    private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      MyResultObject result = (MyResultObject)e.Result;
      // process your result...
    }


ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
JMarsch
  • 21,484
  • 15
  • 77
  • 125
  • It is not necessary to set e.Result in the BackgroundWorker_DoWork method. Set the return type to what you want it to return and the API will set e.Result for you. That is how the API works and every answer in this 11+ year old thread missed that. – jinzai Apr 21 '21 at 03:06
  • Are you thinking of some other class? In the .Net 5 docs, the System.ComponentModel.Background worker uses a delegate of void for the DoWork method. And the docs for .Net 5 indicate to use the .Result member. https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker.dowork?view=net-5.0 https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.doworkeventhandler?view=net-5.0 – JMarsch May 03 '21 at 13:34
1

To add to David's answer, one may want to push a tuple through to provide more than one argument to the methods.

To do so let me update his answer, where a value (called engagementId) is passed through each of the calls and the tuple holds that original item for use as well as the result.

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    var engagementId = (int)e.Argument;
    int result = 2 + 2;
    e.Result = (engagementId, result);
}

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    (int engagementId, int result) tupleResult = ((int, int)) e.Result; // Both (( are needed for tuple/casting.

    MessageBox.Show($"Result received {tupleResult.result} for engagement {tupleResult.engagementId}");
}

See the answer to How To Cast To A Tuple for more information.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • 1
    I would prefer to deconstruct the tuple directly like this: `var (engagementId, result) = ((int, int))e;`. The actual tuple is getting in the way. Only its values are needed. – Theodor Zoulias Jan 18 '21 at 16:31
1

RunWorkerAsync() starts the process asynchronously and will return and continue executing your code before the process actually completes. If you want to obtain the result of the BackgroundWorker, you'll need to create an instance variable to hold that value and check it once the BackgroundWorker completes.

If you want to wait until the work is finished, then you don't need a BackgroundWorker.

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 1
    There's a difference between waiting and _locking up the UI_ while doing so. – T. Sar Mar 09 '17 at 16:14
  • @TSar: That's true, though in the context of working with an older component like `BackgroundWorker` the semantics of "waiting" is relatively complex to accomplish (or at least to demonstrate how it's "waiting"). Something like that is much easier to express with `async` and `await` syntax now and would allow the developer to do away with most use cases for `BackgroundWorker` altogether. – Adam Robinson Mar 09 '17 at 20:49
  • Then it would be nice to complement your answer with the alternatives, instead of just telling the OP to not use the BackgroundWorker. As it reads now, it seems you are suggesting not using any sort of multi-threading at all - and that would end up locking the app until the process is finished. – T. Sar Mar 10 '17 at 11:14
0

Instead of doing the background work in the "DoWork" method, create a method that returns the type you want to return and apply that to e.Result as other answers here recommend. As a minimal example that answers the OP's question without overcomplicating the matter...

        private List<FileInfo> FileInfoWorker(object sender, DoWorkEventArgs e)
    {
        return new List<FileInfo>(new DirectoryInfo("C:\\SOTest").GetFiles().ToList());
    }

    private void bgwTest_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        e.Result = FileInfoWorker(worker, e);
    }

    private void bgwTest_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }
        else if (e.Cancelled)
        {
            tbStatus.Text = "Background operation cancelled.";
        }
        else
        {
            tbStatus.Text = "Background operation complete.";
        }
    }

The example also shows how to update a TextBox from the BackgroundWorker API. Not shown is support for reporting progress via a ProgressBar and TextBoxes and support for cancellation, both are also supported in the API. The code is ran via a button...

        private void btnSOTest_Click(object sender, EventArgs e)
    {
        bgwTest.RunWorkerAsync();
    }
jinzai
  • 436
  • 3
  • 9
0

Depending on your model, you either want to have your worker thread call back to its creator (or to some other process) when it's finished its work, or you have to poll the worker thread every so often to see if it's done and, if so, get the result.

The idea of waiting for a worker thread to return its result undermines the benefits of multithreading.

Welbog
  • 59,154
  • 9
  • 110
  • 123
0

You could have your thread raise an event with the object as an argument:

ThreadFinishedEvent(this, new ThreadEventArgs(object));

where:

public class ThreadEventArgs : EventArgs
{
    public ThreadEventArgs(object object)
    {
        Object = object
    }

    public object Object
    {
        get; private set;
    }
}
ChrisF
  • 134,786
  • 31
  • 255
  • 325
0

Generally speaking when running a process async, The worker thread should call a delegate or fire an event (like ChrisF).

You can check out the new PFX which has some concurrency function that can return values.

For example there is a function called Parallel.ForEach() which has an overload that can return a value.

check this out for more info

http://msdn.microsoft.com/en-us/magazine/cc817396.aspx

Sruly
  • 10,200
  • 6
  • 34
  • 39