pcinfogmach
pcinfogmach

Reputation: 21

Wpf ProgressBar not updating During Threading Operation

i have a C# wpf project with a progress bar. i am trying to update the progressbar during a threading operation but nothing happens. using dispatcher should have done the job but it is not working. here is a sample code: many thanks!

private async void search(string[] files)
{
    progressBar.Maximum = files.Length;
    List<System.Threading.Tasks.Task> searchTasks = new List<System.Threading.Tasks.Task>();
    foreach (string file in files)
    {
        searchTasks.Add(Task.Run(() =>
        {
            this.Dispatcher.Invoke(() =>
            {
                progressBar.Value = progressBar.Value + 1;
                File.ReadAllLines(file);
                //do somthing with the lines
            });
        }));
    }
    await System.Threading.Tasks.Task.WhenAll(searchTasks);
    progressBar.Value = 0;
}

edit: my misatake has been pointed out correctly File.ReadAllLines(file); should be outside the dispatcher

Upvotes: 0

Views: 181

Answers (2)

BionicCode
BionicCode

Reputation: 29028

You are blocking the UI thread because the pseudo parallelized code is executed synchronously on the UI thread. This is because the complete work is posted back to the UI thread by invoking it using the Dispatcher. Dispatcher.InvokeAsync had at least allowed a certain degree of asynchronous execution. No matter those details, the Dispatcher call is absolutely useless in this scenario.
This code is obviously intended to improve the performance, but achieves the exact opposite.

The recommended pattern to report progress is to use the IProgess<T> interface and the Progress<T> default implementation (Progress<T> can also serve as the base class for a custom implmentation, which is usually not required). Progress<T> will take care to execute a registered progress reporting callback on correct thread (SynchronizationContext). Apart from cleaning up your code, this pattern helps to reduce the amount of UI thread related work to a bare minimum, the pure progress reporting.

Note that the binding engine is implicitly marshalling PropertyChanged events to the correct UI thread. This means the binding engine's PropertyChanged callback will always execute on the correct thread (to update the control's property).
Therefore, data binding would eliminate all the hassle as updating a property from any thread is safe in terms of a cross thread exception.

Additionally, never declare async void methods.
The only exception is when the method is an event handler. In every other case return Task instead of void.

private async Task SearchAsync(string[] filePaths)
{
  /* 1) Initialize progress reporting */

  this.progressBar.Maximum = filePaths.Length;

  // Important: Progress<T> instance must be created on the UI thread!
  IProgress<double> progressReporter = new Progress<double>(OnProgressChanged);

  /* 2) Execute progress reporting background task(s) */

  await ProcessFilesInParallelAsync(filePaths, progressReporter);

  /* 3) Finalize progress reporting */

  this.progressBar.Value = 0;
}

// In this case the parameter is ignored as we simply increment the progress value
private void OnProgressChanged(double progressValue)
  => this.progressBar.Value += 1;

private async Task ProcessFilesInParallelAsync(IEnumerable<string> filePaths, IProgress<double> progressReporter)
{
  List<System.Threading.Tasks.Task> searchTasks = new List<System.Threading.Tasks.Task>();
  foreach (string filePath in filePaths)
  {
    Task newFileTask = Task.Run(() => ProcessFile(filePath, progressReporter));
    searchTasks.Add(newFileTask);
  }
    
  await System.Threading.Tasks.Task.WhenAll(searchTasks);
  progressReporter.Report(-1);
}

private void ProcessFile(string filePath, IProgress<double> progressReporter)
{
  string fileContent = File.ReadAllLines(file);
  // do somthing with the lines

  progressReporter.Report(1);
}

Upvotes: 1

JUNAID MUSLA
JUNAID MUSLA

Reputation: 134

The problem with your implementation is you are trying to run list of tasks. When tasks are executed all the task runs simultaneously on a thread pool.

Hence all tasks might get executed at the same time and all gets completed quickly.

The reason you are not able to see UI progress bar update is coz of files are being read on UI thread itself which will hang your Ui. UI won't get updated until all tasks are completed. And when all tasks are completed, you are setting the value to 0 again. Hence not observable to user.

What you could try:

foreach (string file in files)
{
    searchTasks.Add(Task.Run(() =>
    {
        this.Dispatcher.Invoke(() =>
        {
            progressBar.Value = progressBar.Value + 1;
        });
            File.ReadAllLines(file);
            //do somthing with the lines
    }));
}

Upvotes: 0

Related Questions