Introduction
There are process that takes a long time, when is executed in a form the UI became freeze until the work finish.
It’s desirable that user knows that it’s making a long work. We think a Trhead could be do the work, however with BackgroundWorker allows to manage
long process in asynchronous thread notify to user when process finish.
Using the code
To start, we are doing a loop that iterates 3000 times and in each loop sleeps 10 ms. This is the class:
public class LongProcess
{
public void Execute()
{
for (int i = 0; i < 3000; i++)
{
Thread.Sleep(10);
}
}
}
Right, we have a class that is not executed directly in UI. To execute the code we wirte this code in, for example, event handler’s button (WinForms or WPF) or a command ( WPF).
BackgroundWorker worker = new BackgroundWorker();
The DoWork
event manages the process executed asynchronous mode. Right, we have a class that is not executed directly in UI.
To execute the code we wirte this code in, for example, event handler’s button (WinForms or WPF) or a command ( WPF).
BackgroundWorker worker = new BackgroundWorker();
DoWork event manages the process executed asynchronous mode
LongProcess proc = new LongProcess();
worker.DoWork += new DoWorkEventHandler((asyncSender, args) =>
{
proc.Execute();
});
Inside DoWork
methods we shouldn’t manage the controls’ state (enable or disable buttons, show information in labels, show the wait cursor, etc.).
If we make it .NET throws an exception. We should make this before executed the asynchronous work. Now we have to write an event handler
that raises when the process finish, we use RunWorkerCompleted
.
worker.RunWorkerCompleted+=new RunWorkerCompletedEventHandler((asyncSender, args) =>
{
if (args.Error != null)
{
}
else
{
}
});
To know if there has been we must ask if args.Error!=null
. And this event handler is the place to enable/disable controls, show messages, etc.
Finally to execute the task:
worker.RunWorkerAsync();
Notify to user progress and decoupling to UI
Now the user knows that a long process is executing. However don’t know the progress but we know the progress and we wish to show to the user.
With ProgressChanged
event manages when BakgroundWorker
have notified progress information. If we call to ReportProgreess
method the event raises and we update a progressbar, for example.
But we only know the progress in LongProcess
and we have BackgroundWorker
’s object in UI. A parameter in Execute method with BakgroundWorker or move code to UI is not the better solutions.
What is a better solution? An event handler in our class (that could be a business class for example. Here is the solution:
First we are creating a class to pass information to event handler:
public class LongProcessEventArgs:EventArgs
{
public int Percentage { get; set; }
public LongProcessEventArgs(int percentage)
{
this.Percentage = percentage;
}
}
Now we create the event handler in the class. With this, decoupling the progress with the UI:
public EventHandler<longprocesseventargs> LongProcessEvent;
Now the Execute
method:
public void Execute()
{
for (int i = 0; i < 3000; i++)
{
int percentage = (100 * i / 3000);
if (LongProcessEvent == null)
{
LongProcessEvent(this, new LongProcessEventArgs(percentage));
}
Thread.Sleep(100);
}
}
Last, in the view:
worker.WorkerReportsProgress = true;
proc.LongProcessEvent += new EventHandler<longprocesseventargs>((processSender, args) =>
{
worker.ReportProgress(args.Percentage);
});
worker.ProgressChanged += new ProgressChangedEventHandler((asyncSender, args) =>
{
progress.Value = args.ProgressPercentage;
});
Conclusion
It's esay decouple the logic to calculate and show to user the remain time of a long process.