简体   繁体   中英

BackgroundWorker - reporting progress with "sub tasks"

A WinForms application with a custom control, LabelProgressBar , which has the ability to display both progress and some descriptive text and/or percentage completion. This is done by calling LabelProgressBar.statusInProgress(string message, int percentageCompletion) .

One usage of this is as follows:

private void import_begin(System.Object sender, System.ComponentModel.DoWorkEventArgs args)
{
    // first unpack the arguments
    System.Object[] arguments = (System.Object[])args.Argument;
    System.String filename = (System.String)arguments[0];
    System.String why = (System.String)arguments[1];

     // tasks:
     // 1. read excel file and apply changes to model
     // 2. gather changes and format them as XML
     // 3. send request to server
     // 4. commit/rollback changes

      // grab the worker thread so we can report percentage progress
      System.ComponentModel.BackgroundWorker worker = (System.ComponentModel.BackgroundWorker)sender;

     // now do the work
     #region Task1
     Controller.Excel excel = new Controller.Excel(filename);
     try
     {
          // the progress of this needs to be tracked
          overall_result = excel.import_all(out modified_nodes);
     }
     catch (InvalidDataExcetpion invDataEx)
     {
         // deal with it
     }
     #endregion
     worker.ReportProgress(25);

     // complete remaining tasks...
}

The event handler for the worker reporting its progress is the following:

private void import_progress(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
    Debug.WriteLine("Import percentage completion: " + e.ProgressPercentage);
    labelProgressBar1.statusInProgress("Import", e.ProgressPercentage);
}

In short, the import_begin method is broken up into several "tasks". These are broken up into "subtasks". Taking the example of the import_all method:

public Command_Result import_all(out System.Collections.Generic.List<Model.Data_Node> nodes)
        {
            Command_Result overall_result = Command_Result.OK;
            Command_Result this_result;
            nodes = new System.Collections.Generic.List<Model.Data_Node>(excel.Workbook.Worksheets.Count);
            Model.Data_Node destination;

            // the intent is to report the progress of this particular subtask on the basis of how many worksheets have been processed in this for loop
            foreach (OfficeOpenXml.ExcelWorksheet worksheet in excel.Workbook.Worksheets)
            {
                this_result = import_sheet(worksheet.Name, out destination);
                nodes.Add(destination);
                if (this_result > overall_result)
                {
                    overall_result = this_result;
                }
            }

            return overall_result;
        }

The intent is to have this "subtask" report progress on the basis of how many sheets have been processed in the loop. Calculating a percentage for this is a trivial task, but it is not clear to me how this can be reported back to the import_begin method. When this "subtask" is completed, the overall task completion (from the POV of the import_begin method) should be 25%. Similarly for the other tasks. How can this be achieved?

import_begin don't really need to get the update, it can just call the subtasks, while also passing the BackgroundWorker , so the subtasks are responsible to directly report their progress. If "polluting" the subtasks with BackgroundWorker is unacceptable, then create a delegate to call the BackgroundWorker, so your subtasks will then call the delegate instead.

private void mainTask(object sender, DoWorkEventArgs e)
{
    var worker = (BackgroundWorker)sender;
    var report = new Action<int>(i => worker.ReportProgress(i)); //the delegate
    smallTask1Clean(report); //this one pass the delegate
    smallTask2(worker); //this one directly call background worker
    worker.ReportProgress(100);
}

void smallTask1Clean(Action<int> a)
{
    for (int i = 0; i < 20; i++)
    {
        Thread.Sleep(500);
        a(i);
    }
}

void smallTask2(BackgroundWorker w)
{
    for (int i = 0; i < 5; i++)
    {
        Thread.Sleep(1000);
        w.ReportProgress(i*80/5+20);
    }
}

You can also insulate the subtasks from having to know their part in the larger tasks, in this case, the delegate should take two variables, the current internal progress of the subtasks and the total item it needs to process.

private void mainTask(object sender, DoWorkEventArgs e)
{
    var worker = (BackgroundWorker)sender;
    var preTaskProgress = 0;
    var currentTaskTotalPercentage = 0;
    var smarterDelegate = new Action<int, int>((current, total) =>
     {
         worker.ReportProgress(preTaskProgress + (current *currentTaskTotalPercentage/total));
     });

    currentTaskTotalPercentage = 30; //the following task will in total progressed the main task for 30%
    smallTaskClean(smarterDelegate);
    preTaskProgress = currentTaskTotalPercentage; //upate the main the progress before starting the next task
    currentTaskTotalPercentage = 70; //the following task will in total progressed the main task for 70%
    smallTaskClean(smarterDelegate);

    worker.ReportProgress(100);
}

void smallTaskClean(Action<int,int> a)
{
    for (int i = 0; i < 5; i++)
    {
        Thread.Sleep(1500);
        a(i,5);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM