I am trying to understand better how to update ProgressBar Value using async and await.
I can update the progress bar value using async if progress bar is on UI main thread. But i am having modal dialog window which contains progress bar within it and this modal dialog pops up when I click on a button.
Now I want to update value of this progress bar from Async method.
How to update modal dialog progress bar from async method?
P.S - I don't want to use BackgroundWorker.
Assuming you are running a WPF app you can do that by calling the current dispatcher
private void PushOnUIThread(Action action)
{
if (Application.Current.Dispatcher.CheckAccess())
{
action();
}
else
{
Application.Current.Dispatcher.Invoke(action);
}
}
Then you can call
PushOnUIThread(()=> progressBar.Value = 30);
I think what you're looking for can be accomplished with IProgress<T>. IProgress<T> and the default implementation Progress<T> is the way to report progress from another context.
Have a look at: Reporting Progress from Async Tasks
And a sample from the post by #StephenCleary
public async void StartProcessingButton_Click(object sender, EventArgs e)
{
// The Progress<T> constructor captures our UI context,
// so the lambda will be run on the UI thread.
var progress = new Progress<int>(percent =>
{
textBox1.Text = percent + "%";
});
// DoProcessing is run on the thread pool.
await Task.Run(() => DoProcessing(progress));
textBox1.Text = "Done!";
}
public void DoProcessing(IProgress<int> progress)
{
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
if (progress != null)
progress.Report(i);
}
}
Related
Please could someone suggest why the following doesn't work? All I want to do is display an indeterminate progress bar which starts when the button is clicked, then after I've done some work I set the indeterminate progress bar to false to stop it.
However when I run the code below the indeterminate progress bar doesn't even start. I tried commenting out the line this.progressBar.IsIndeterminate = false and when I do this the progress bar does start but then doesn't terminate.
private void GenerateCSV_Click(object sender, RoutedEventArgs e)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate ()
{
this.progressBar.IsIndeterminate = true;
// do some work
Thread.Sleep(10 * 1000);
this.progressBar.IsIndeterminate = false;
}));
}
Your code can't work because the "do some work" is happening on the same Thread on which the UI works. So, if that Thread is busy with the "work", how can it handle the UI animation for the ProgressBar at the same time?
You have to put the "work" on another Thread, so the UI Thread is free and can do its job with the ProgressBar (or other UI controls).
1) create a method that does the work and returns a Task, so it can be "awaited" for completion:
private async Task DoWorkAsync()
{
await Task.Run(() =>
{
//do some work HERE
Thread.Sleep(2000);
});
}
2) Put the async modifier on the GenerateCSV_Click:
private async void GenerateCSV_Click(object sender, RoutedEventArgs e)
3) throw away all that "Dispatcher" / "Invoke" etc stuffs, and use the DoWorkAsync this way:
private async void Button_Click(object sender, RoutedEventArgs e)
{
PB.IsIndeterminate = true;
await DoWorkAsync();
PB.IsIndeterminate = false;
}
So, what's happening now? When GenerateCSV_Click encounters the first await... it begins to work automatically on another Thread, leaving the UI Thread free to operate and animate the ProgressBar.
When the work is finished, the control of the method returns to the UI Thread that sets the IsIndeterminate to false.
Here you can find the MSDN tutorial on async programming:
https://msdn.microsoft.com/en-us/library/mt674882.aspx
If you Google "C# async await", you'll find dozens of tutorials, examples and explanations ;)
I have a simly Windows Forms app with just a button and a progressbar on it.
Then I have this code:
private async void buttonStart_Click(object sender, EventArgs e)
{
progressBar.Minimum = 0;
progressBar.Maximum = 5;
progressBar.Step = 1;
progressBar.Value = 0;
await ConvertFiles();
MessageBox.Show("ok");
}
private async Task ConvertFiles()
{
await Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
System.Threading.Thread.Sleep(1000);
Invoke(new Action(() => progressBar.PerformStep()));
}
});
}
The await ConvertFiles(); returns too early, the ok messagebox already appears at about 80% progress.
What am I doing wrong?
The problem you are experiencing is not related to the async/await, which you use correctly. The await is not returning too early, just the progress bar updates too late. In other words, this is a progress bar control specific problem described in a several threads - Disabling .NET progressbar animation when changing value?, Disable WinForms ProgressBar animation, The RunWorkerCompleted is triggered before the progressbar reaches 100% etc. You can use one of the workarounds provided in those threads.
Just to be safe why not move the
MessageBox.Show("ok");
into a Continuewith so:
await ConvertFiles().ContinueWith((t) => { MessageBox.Show("ok"); });
this makes sure it only runs when the task is complete
I implemented the custom progressbar indicator in my Windows Phone 8 project. It works fine if I try to toggle the indicator with a button. But of course I want it to show up while I perform time consuming actions (filling a list with many items). But as it blocks the UI the progressbar indicator doesn't show up before the action but only afterwards. I tried .UpdateLayout() on the indicator itself and the whole page before performing modifications to the list but none of it worked.
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
// add ~100 list items
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
Is there any other way to do this?
You could offload your time consuming work to a new task and add a continuation to set progress bar visibility at the end. Here i'm using the Task Parallel Library to achieve this:
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
Task.Run(() =>
{
// Do CPU intensive work
}).ContinueWith(task =>
{
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
}, TaskScheduler.FromCurrentSynchronizationContext());
You should run your heavy job asynchronously (more about async at MSDN and at the Stephen Cleary Blog) - so that it won't block UI.
The very simple example where you have a ProgressBar and a heavy Task which will inform PBar about its progress can look like this: (I've subscribed the start of the method to Button Click)
private async void StartBtn_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>( (p) =>
{
progresPB.Value = p;
});
await DoSomething(progress); // start asynchronously Task with progress indication
}
private Task<bool> DoSomething(IProgress<double> progress)
{
TaskCompletionSource<bool> taskComplete = new TaskCompletionSource<bool>();
// run your heavy task asynchronous
Task.Run(async () =>
{
for (int i = 0; i < 10; i++) // work divided into parts
{
await Task.Delay(1000); // some heavy work
progress.Report((double)i / 10);
}
taskComplete.TrySetResult(true);
});
return taskComplete.Task;
}
I know that this question had been asked 100 times before, but all the answers I read didn't worked for me. So, I'll try my luck and ask again.
I have a SliderBar that calls a method on the ValueChanged event.
In this method I do some stuff that takes time, and I want that in this time the user will see an "working" ProgressBar (IsIndeterminate=true).
Unfortunately, I don't succeed to make the ProgressBar start working (in the UI) until all the method loops finished.
I tried threads, BackgroundWorker and async Tasks but with no success..
What am I doing wrong?
This is the method:
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e){
WorkingBar.IsIndeterminate = true; //change the progressBar
SqlAThread = new Thread(SqlAStart);
SqlAThread.Start();
}
The thread:
private void SqlAStart()
{
... //do some stuff
}
In Slider_ValueChanged you start a new tread to run the time-consuming SqlAStart() method, but SqlAStart immediately pushes the work back to the UI thread (via Dispatcher.Invoke()). Therefore, your UI hangs and you don't see any ProgressBar progress until the work is done.
In SqlAStart, only do a Dispatcher.Invoke() where you need to update the progress bar:
private void SqlAStart()
{
ServerThread = new Thread(HttpSrv.listen);
ServerThread.Start();
...
this.Dispatcher.Invoke((Action)(() => {
WorkingBar.Value = ...
}));
....
}
This is quite easy using IProgress<T>:
private async void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (State.Value > 0)
{
var progress = new Progress<int?>(value =>
{
WorkingBar.IsIndeterminate = (value == null);
if (value != null)
WorkingBar.Value = value.Value;
});
await Task.Run(() => doWork(progress));
Task.Run(SqlAStart);
}
...
}
private void SqlAStart(IProgress<int?> progress)
{
ServerTask = Task.Run(HttpSrv.listen);
...
}
private void doWork(IProgress<int?> progress)
{
if (progress != null)
progress.Report(null);
...
}
Note that you should (almost) never use Dispatcher, Thread, or BackgroundWorker in modern applications.
Rather than type out the whole lengthy process, I'd invite you to take a look at my answer to the Progress Bar update from Background worker stalling question here on StackOverflow. It demonstrates how to correctly use a BackgroundWorker object to update the progress value of a ProgressBar.
You may also like to view the BackgroundWorker Class page on MSDN for more detailed information.
I'm having this simple code:
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
var progress = new Progress<int>();
progress.ProgressChanged += (a, b) =>
{
this.progressBar.Value = b;
};
// this is blocking
await this.LongRunOpAsync(filepath, progress);
// this is not blocking
// await this.LongRunOpAsync(filepath, null);
}
public Task LongRunOpAsync(string filename, IProgress<int> progress)
{
return Task.Run(() =>
{
using (var ops = new LongOps())
{
ops.LongRunOp(filename, progress);
}
});
}
Once I click my button the UI is still blocked from the long running operation. If I don't use the Progress and instead give my long running operation null as the second parameter the UI isn't blocking. I'm quite sure this "error" is due to some misunderstanding I have about async/await and threads.
The code you've shown won't block the UI thread.
In fact, as shown, it doesn't need async/await - so I'm assuming this is not the actual code.
You need to look at what ops.LongRunOp does with the progress function.
I suspect it marshals progress back to the UI thread - so it can access UI controls.
If it does this too often and too quickly, it will swamp the UI thread and make the app unresponsive.