Search code examples
c#winformsasynchronousprogress-barthreadpool

ProgressBar with IProgress<T> delegate across threads does not update


I'm trying to display the progress of unzipping a few files on a WinForms ProgressBar.

Here I create a System.Progress with a handler that updates the ProgressBar

Progress<int> progress = new Progress<int>(value => {
    progressBar1.Value = value; progressBar1.Update(); }); 

Then I hand over my function to the thread pool.

Task t = Task.Run(() => FileUtils.UnzipTo(targetDir,
sourceDir, false, progress));

t.Wait();

Inside my unzip function I do this for every file in the archive:

progress.Report(++complete / total * 100);

This is definitely called and if I use a method for my handler the breakpoint is hit for every file (although too late I think)

I was hoping this would update the ProgressBar.

I see the dialog until the file is completely decompressed with a busy cursor above but there is no increase in progress.

What am I missing?


Solution

  • I believe that "what you're missing" is the integer division in the expression progress.Report(++complete / total * 100). When I replicated the issue this expression always evaluates to zero unless the total variable is cast to a floating point variable type.

    Other than that I was able to reproduce a mock version of your code with the desired outcome.

    progress

    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            buttonUnzip.Click += onButtonUnzip;
        }
        private async void onButtonUnzip(object? sender, EventArgs e)
        {
            progressBar1.Visible = true;
            Progress<int> progress = new Progress<int>(value => {
                progressBar1.Value = value; progressBar1.Update();
            });
            await Task.Run(() => FileUtils.UnzipTo("mockSource", "mockDest", true, progress));
            progressBar1.Visible = false;
        }
    }
    class FileUtils
    {
        static string[] GetFilesMock() => new string[25];
        static void UnzipOneMock(string sourceDir, string targetDir, string file) 
            => Thread.Sleep(TimeSpan.FromMilliseconds(100));
        public static void UnzipTo(
            string sourceDir,
            string targetDir,
            bool option, 
            IProgress<int> progress)
        {
            var files = GetFilesMock();
            int complete = 0;
            int total = files.Length;
            foreach (var file in files)
            {
                UnzipOneMock(sourceDir, targetDir, file);
                progress.Report((int)(++complete / (double)total * 100));
            }
        }
    }