Thursday, July 18, 2013

Timely GUI update of progress for background worker process in .NET

Sometime when you are performing a very expensive process using a background working in .NET, for example processing records from the database one by one, you want the GUI to display update on the progress of the progress, typical code will look like to following:

BackgroundWorker worker=new BackgroundWorker();
worker.WorkerReportsProgress=true;

bool is_running=true;

int total_record_count=EmployeeDb.GetDbRecordCount(criteria)
worker.DoWork+=(s1, e1)=>
{
    EmployeeDb.DoAnalysis(criteria, (rec_index)=>
      {
           worker.ReportProgress(rec_index * 100 / total_record_count);
           return is_running;
      });
};
worker.ProgressChanged+=(s1, e1)=>
{
    progressBar1.Value=e1.ProgressPercentage;
};
worker.RunWorkerCompleted+=(s1, e1)=>
{
   MessageBox.Show("Done!");
};

worker.RunWorkerAsync();

The problem with the above method is that we may not know how frequently the worker.ReportProgress() is called, and within what time interval two consecutive worker.ReportProgress() are called. This can be bad for two reason: firstly, if the time interval between two consecutive calls to worker.ReportProgress() is very short, the GUI may not get updated since it a low priority task; secondly, two many calls to worker.ReportProgress() wasting CPU cycles while present no real difference to the user.

My solution at the moment is to timely call worker.ReportProgress() instead, e.g. by every 1 seconds. This is very easily done. the following shows the timely call procedure (the change is in the bold highlight):

BackgroundWorker worker=new BackgroundWorker();
worker.WorkerReportsProgress=true;

bool is_running=true;

DateTime current_time=DateTime.Now;
DateTime report_time=DateTime.Now;
TimeSpan ts;

int total_record_count=EmployeeDb.GetDbRecordCount(criteria)
worker.DoWork+=(s1, e1)=>
{
    EmployeeDb.DoAnalysis(criteria, (rec_index)=>
      {
           current_time=DateTime.Now;
           ts=current_time - report_time;
           if(ts.TotalMilliSeconds > 1000)
           {
                  report_time=current_time;
                  worker.ReportProgress(rec_index * 100 / total_record_count);
           }
           return is_running;
      });
};
worker.ProgressChanged+=(s1, e1)=>
{
    progressBar1.Value=e1.ProgressPercentage;
};
worker.RunWorkerCompleted+=(s1, e1)=>
{
   MessageBox.Show("Done!");
};

worker.RunWorkerAsync();



No comments:

Post a Comment