Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Run process in background and decouple UI

4.77/5 (8 votes)
24 Apr 2012CPOL2 min read 32.6K   748  
How to notify users the progress of a long task without mixing UI with the business.

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:

C#
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).

C#
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).

C#
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.

C#
worker.RunWorkerCompleted+=new RunWorkerCompletedEventHandler((asyncSender, args) =>
{
    if (args.Error != null)
    {
        //Error message
    }
    else
    {
        //Success message
    }
});

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:

C#
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:

C#
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:

C#
public EventHandler<longprocesseventargs> LongProcessEvent;

Now the Execute method:

C#
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:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)