Reputation: 21
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
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
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