Search code examples
c#user-interfacebackgroundworker

How to use a BackgroundWorker to update multiple labels?


This is a follow up question to Updating a dialog from another form (The code and screenshots can be found there)

To solve my GUI hanging problem I received 2 recommendations:

  • Using Application.DoEvents()

  • Using a BackgroundWorker

The DoEvents() approach works, however it has been pointed out that I should not use it. Indeed, I notice that the GUI updates correctly but is unresponsive for short times.

That's why I want to use a BackgroundWorker and have read up on it.

I don't understand how I would implement it so that it can be used to update the 4 labels in my example code separately, though. I want to show the progress (and update 4 dialog labels) as the program successfully finishes one job. The BackgroundWorker has only 1 DoWork() though. I have tried to use the e.Argument of the DoWorkEventArgs to differentiate between the different update methods but that attempt had failed.

public partial class BackgroundWorkerImportStatusDialog : Form
{
    private BackgroundWorker dialogWorker = new BackgroundWorker();

    private string path;
    private string clientName;

    public BackgroundWorkerImportStatusDialog()
    {
        InitializeComponent();
    }

    public void updateFileStatus(string path)
    {
        this.path = path;

        dialogWorker = new BackgroundWorker();
        dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
        dialogWorker.RunWorkerAsync(UpdateComponent.FileStatus);

    }

    public void updatePrintStatus()
    {
        dialogWorker = new BackgroundWorker();
        dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
        dialogWorker.RunWorkerAsync(UpdateComponent.PrintStatus);
    }

    public void updateImportStatus(string clientName)
    {
        this.clientName = clientName;

        dialogWorker = new BackgroundWorker();
        dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
        dialogWorker.RunWorkerAsync(UpdateComponent.ImportStatus);
    }

    public void updateArchiveStatus()
    {
        dialogWorker = new BackgroundWorker();
        dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
        dialogWorker.RunWorkerAsync(UpdateComponent.ArchiveStatus);
    }

    private void updateLabels(object sender, DoWorkEventArgs e)
    {
        MessageBox.Show(e.Argument.ToString());
        if ((UpdateComponent) e.Argument == UpdateComponent.FileStatus)
        {
            t_filename.Text = path;
        }
        if ((UpdateComponent) e.Argument == UpdateComponent.PrintStatus)
        {
            t_printed.Text = "sent to printer";
        }
        if ((UpdateComponent) e.Argument == UpdateComponent.ImportStatus)
        {
            t_client.Text = clientName;
        }
        if ((UpdateComponent) e.Argument == UpdateComponent.ArchiveStatus)
        {
            t_archived.Text = "archived";
        }
    }

    public enum UpdateComponent { FileStatus, PrintStatus, ImportStatus, ArchiveStatus}

And I can't imagine having 4 BackgroundWorkers for this pretty trivial dialog is the solution.


Solution

  • As I understand your question, you want to have your dialog form inform the user about 4 different aspects of your application running:

    • printing status
    • file status
    • import status
    • archiver status

    Background worker could be used to periodically check each one. You may advanced progressbar by 25% after status of each operation is checked (and update your UI with appropriate information).

    You may also try async programming - i.e. just start the operation, and lets your application continue. When the operation completes, your application will be notified, and could update information on the form. Depending on the .NET framework you're using you may use async and await (avaialble since .NET 4.5 / C# 5 - async & await on MSDN) or classic approach to asynchronous programming.


    Edit:

    I am not sure that BackgroundWorker is the best solution in this situation. I can imagine having something like:

    1. BackhgroundWorker checking things just once - i.e. check printing status once, file status once, import status once, archiver status once. This may sound silly, but it could be user behavior driver - i.e. explicitly launched when user clicks or invokes this mechanism any other way. ProgressBar could be put on the application's statausbar, so that user knows that 'application is actually doing something'.

    2. Previous approach could be improved a bit - you never actually finish your job in BackgroundWorker - instead inside your main method you just have an infinite loop. This will allow you to check things periodically. In this approach there is no point in increasing the progress.

    Sample for the second approach:

    private void bg_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        for (int i = 1; i <= 10; i++)
        {
            if (worker.CancellationPending == true)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                CheckPrintingStatus();
                CheckFileStatus();
                CheckImportStatus();
                CheckArchiverStatus();
                System.Threading.Thread.Sleep(5000); // sleep for 5 seconds
            }
        }
    }
    

    There is a question if this solution (second approach) is better than having a thread created explicitly. You could think of creating 4 different threads, so that each could check something else. This would be a bit heavier on the OS, but on the other hand you can set different sleep times for every operation.

    If you go for bare threads - you may want to use ThreadPool instead of creating threads explicitly.