Search code examples
c#wpfmultithreadingbackgroundworkerdispatchertimer

Using DispatcherTimer with a BackgroundWorker and a ProgressBar


I've created a simple app that adds 10 times a string to a ListView every second and displays it. All this is done using a BackgroundWorker and a ProgressBar that shows the adding progress. My question is, since I've heard that pausing a thread is very dangerous and can lead to several problems, how can I replace the Thread.Sleep with let's say a DispatcherTimer? What if I would add 1000000 elements to this list and report the progress? Is it better to use Thread.Sleep(1) or something else in this case?

My code is the following:

    private BackgroundWorker worker = new BackgroundWorker();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnAggiungi_Click(object sender, RoutedEventArgs e)
    {
        worker.WorkerReportsProgress = true;
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        worker.RunWorkerAsync();
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
        int min;
        int max = 10;

        for (min = 1; min <= max; min++)
        {
            int progressPercentage = Convert.ToInt32((double)min / max * 100);
            worker.ReportProgress(progressPercentage, text);
            Thread.Sleep(100);
        }
    }

    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pb.Value = e.ProgressPercentage;
        lstLoremIpsum.Items.Add(e.UserState.ToString());
    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        lblCompleted.Content = "Done";
    }

Screenshot:


Solution

  • After some testing:

    worker.ReportProgress is meant to report the overall progress of the BackgroundWorker back to the UI thread. This way you can display the progress and inform your users. However, you're using it to actually add strings to an listbox/ listview. You're better off compiling adding the strings (text) in memory (buffer in a list/ array) and then populating listbox/ listview once when the background worker completes. If your GUI still freezes, add the strings per 50, 100 or 200.

    And another minor improvement:

    private void UpdateProgressbar(int percentage)
    {
        if (InvokeRequired)
        {
            this.BeginInvoke(new Action<int>(UpdateProgressbar), new object[] { percentage });
            return;
        }
    
        pb.Value = percentage;
    }
    
    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        UpdateProgressbar(e.ProgressPercentage);           
    }