0

I'm trying (yet again) to implement a Backgroundworker in my app so that the UI will be responsive. The user selects files to be processed which end up on a grid. When the processing starts it's like this:

for (int i = 0; i<Grid.Rows.Count; i++)
{

    Worker work = new Worker();
    //set up data for processing based on the current row in the grid
    bw.RunWorkerAsync(work);
    addStatusToGrid();
    //clean up; indicate work on this file is done
    work=null;


}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
    Worker work = (Worker)e.Argument;

    if (work.doTheWork() == false)
    {
        //indicate failure

    }
    else
    {
        //indicate success
    }
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //not sure what to do here
}

BUT what happens is that as soon as bw.RunWorkerAsync( ) is called the app immediately goes to the next line in the for loop and then starts the same process again. Now, I sort of want this but I get an error message, "This backgroundworker is currently busy and cannot run multiple tasks concurrently". If I just process one item, addStatusToGrid() gets called immediately and of course there is no status to add as the work is not done.

SEVERAL QUESTIONS: If I can use this mechanism to initiate several processing sessions at once, that would be a good thing. But how do I prevent addStatusToGrid() getting called immediately?

Right now, the UI does get updated a lot. I have a progress bar and it often shows updates. I cannot cancel the operations with a cancel button I guess because the UI thread is busy.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Ron
  • 2,435
  • 3
  • 25
  • 34
  • How many rows need to be processed in this way? – Steve Jan 06 '14 at 00:04
  • 1
    well in a nut shell you should be updating the grid in the completed event. ie kick off the background job(s). As each one completes the results are displayed. – Tony Hopkinson Jan 06 '14 at 00:09
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Jan 06 '14 at 01:09
  • Is there any reason why you don't want to use the task class instead of the background worker? It doesn't appear that you are providing status updates to the UI (easy to do with bw). As stated in an answer below the size of the data / grid and processing time required will determine the best approach on when to update the UI. – tsells Jan 06 '14 at 04:09

1 Answers1

0

From your question it appears that you have multiple rows in the grid that need to be processed. Now the background worker executes the method in another thread thus freeing up the UI to perform additional actions. (IE status updates). As this is executed on another thread the UI is free to continue processing as normal. The UI thread will not wait for the BackgroundWorker to finish. If it did wait, there would be no use using the background worker. Additionally the BackgroundWorker must finish the operation before starting another operation (thus your error message).

Now it is hard to understand what you are doing in the work.doTheWork() but I suspect you would want to take another approach here.

Before we begin tackling the question the first thing you must understand is the Cancellation of a background worker doesn't actually cancel the thread but provides a "flag" for your code to check if the BackgroundWorker is pending calculation (MSDN on this flag ).

Moving forward. As the Grid is held on the UI thread you need to understand the data from the grid that we want to capture and pass it into the BackgroundWorker context. As this inst provided I am going to do a very basic example. Now you could run multiple background workers to process each row individually thus spawning 1 worker per row in the Grid. For your requirement this may be ideal but for larger grids you are essentially creating 1 thread per row to process and in a grid with hundreds of rows this could be disastrous.

So to being you can create a method something like below. I have commented the code to help you run through it. Bascially this shows the ability to cancel the worker, report progress and iterate through each row item individually. I also added some basic classes for use inside the worker. [Note this is demo code only]

class DoWorkTester
{
    int currentIndex = 0;
    GridRow[] rows;


    public void ExecuteWorkers()
    {

        GridRow rowA = new GridRow
        {
            PropertyA = "abc",
            PropertyB = "def"
        };

        GridRow rowB = new GridRow
        {
            PropertyA = "123",
            PropertyB = "456"
        };

        GridRow rowC = new GridRow
        {
            PropertyA = "xyz",
            PropertyB = "789"
        };

        rows = new GridRow[] { rowA, rowB, rowC };

        currentIndex = 0;

        runWorkers();
    }

    BackgroundWorker worker;

    void runWorkers()
    {
        //done all rows
        if (currentIndex >= rows.Length - 1)
            return;

        //is the worker busy?
        if (worker != null && worker.IsBusy)
        {
            //TODO: Trying to execute on a running worker.
            return;
        }

        //create a new worker
        worker = new BackgroundWorker();
        worker.WorkerSupportsCancellation = true;
        worker.WorkerReportsProgress = true;
        worker.ProgressChanged += (o, e) =>
        {
            //TODO: Update the UI that the progress has changed
        };
        worker.DoWork += (o, e) =>
        {
            if (currentIndex >= rows.Length - 1)
            {
                //indicate done
                e.Result = new WorkResult
                {
                    Message = "",
                    Status = WorkerResultStatus.DONE
                };
                return;
            }
            //check if the worker is cancelling
            else if (worker.CancellationPending)
            {
                e.Result = WorkResult.Cancelled;
                return;
            }
            currentIndex++;

            //report the progress to the UI thread.
            worker.ReportProgress(currentIndex);

            //TODO: Execute your logic.
            if (worker.CancellationPending)
            {
                e.Result = WorkResult.Cancelled;
                return;
            }

            e.Result = WorkResult.Completed;
        };
        worker.RunWorkerCompleted += (o, e) =>
        {
            var result = e.Result as WorkResult;
            if (result == null || result.Status != WorkerResultStatus.DONE)
            {
                //TODO: Code for cancelled  \ failed results
                worker.Dispose();
                worker = null;
                return;
            }
            //Re-call the run workers thread
            runWorkers();
        };
        worker.RunWorkerAsync(rows[currentIndex]);
    }

    /// <summary>
    /// cancel the worker
    /// </summary>
    void cancelWorker()
    {
        //is the worker set to an instance and is it busy?
        if (worker != null && worker.IsBusy)
            worker.CancelAsync();

    }
}

enum WorkerResultStatus
{
    DONE,
    CANCELLED,
    FAILED
}

class WorkResult
{
    public string Message { get; set; }
    public WorkerResultStatus Status { get; set; }

    public static WorkResult Completed
    {
        get
        {
            return new WorkResult
            {
                Status = WorkerResultStatus.DONE,
                Message = ""
            };
        }
    }

    public static WorkResult Cancelled
    {
        get
        {
            return new WorkResult
            {
                Message = "Cancelled by user",
                Status = WorkerResultStatus.CANCELLED
            };
        }
    }
}


class GridRow
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
}

Now if you wanted to process multiple rows at a time you will have to adapt the code to use some sort of Worker Pooling or pass all the row data to the first background worker thus removing the recursion.

Cheers.

Nico
  • 12,493
  • 5
  • 42
  • 62
  • This will require more than a small change to the logic. Yes, I would be processing multiple rows. I could encapsulate the row data as objects and pass an array of them; would not be too much of a hit on memory. I would have to ensure that if multiple threads were spawned they would be limited somehow as there could be 1000s of rows. Unlikely but possible. Spawning more threads than cores doesn't make sense as they would just wait. – Ron Jan 06 '14 at 06:30