Custom ProgressBar Indicator not showing up before time consuming action - c#

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;
}

Related

Can't understand how to use Progress(T) to update UI during async process

I have been googling this for quite a few hours, and read quite a few SO questions where this is discussed but I am sorry to say I just don't get how to use it.
Basically what I am trying to do is to have a label in a WPF/Win Forms app display the following while an async task is running:
Processing .
and at each 1 second interval to add another dot until I get to three and then start over at 1 until the task is done.
As a first step I am only trying to add a dot after each second and have tried it with an IProgress action but the only thing that I have been able to accomplish is either nothing or the label gets populated with dots in one shot and the other task seems to run after that is done.
I next tried doing the following:
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Text = "Waiting for the response ...";
startButton.IsEnabled = false;
resultsTextBox.Clear();
var task = SumPageSizesAsync();
var progress = Task.Run(() =>
{
var aTimer = new System.Timers.Timer(1000);
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = true;
aTimer.Enabled = true;
void OnTimedEvent(object source, ElapsedEventArgs et)
{
if (!lblProgress.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(() =>
{
lblProgress.Content += ".";
});
}
}
});
await task;
await progress;
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
startButton.IsEnabled = true;
}
But again the label just gets populated with dots at once while the other task keeps running.
I took this example from the Microsoft Docs
UPDATE:
I have now tried removing the loop while(!task.IsComplete) which basically makes the label start to be updated after the first task has finished. Then I tried to the following:
var task = SumPageSizesAsync();
var progress = GetProgress();
await Task.WhenAll(SumPageSizesAsync(), GetProgress());
But got the same result, the label begins to update after the first task has concluded.
Thank you for your help.
"Progress(T)" is the wrong pattern for this.
Here is the code for a WPF application that does this with 100% async / await code, no additional threads are created.
It starts two async tasks. The first simulates the long running async process. The second one starts another async Task that takes the first task as a parameter. It loops until the first task is completed, while updating a label with a "..." pattern. It awaits a Task.Delay to control the animation speed.
Both those tasks are placed in to a list, and the we await the completion of both of them.
This could all be wrapped up in in to a ShowProgressUntilTaskCompletes method (or extension method) that takes the worker Task as a parameter, which gives you an easily reusable method of showing a progress indicator for any Task.
MainWindow.xaml:
<Window
x:Class="LongProcessDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<StackPanel Margin="100" Orientation="Vertical">
<Button Click="StartProcess_OnClick" Content="Start" />
<TextBlock
Name="LoadingText"
Padding="20"
Text="Not Running"
TextAlignment="Center" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
namespace LongProcessDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void StartProcess_OnClick(object sender, RoutedEventArgs e)
{
var longRunningTask = SimulateLongRunningTask();
var spinner = ShowSpinner(longRunningTask);
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
private async Task ShowSpinner(Task longRunningTask)
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
LoadingText.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
LoadingText.Text = "Done!";
}
private async Task SimulateLongRunningTask()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
Here is a recording of it running, with window interaction proving that the UI is not blocked.
As an extra bonus, I got bored and implemented the extension method I mentioned (with super special bonus, a "local function" feature from C# 7!).
public static class TaskExtensions
{
public static async Task WithSpinner(this Task longRunningTask, TextBlock spinnerTextBox)
{
async Task ShowSpinner()
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
spinnerTextBox.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
spinnerTextBox.Text = "Done!";
}
var spinner = ShowSpinner();
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
}
You use it like this:
await SimulateLongRunningTask().WithSpinner(LoadingTextBlock);
If you use the await, it means that your code will wait for the async operation to finish at that line, and then continue.
That is why your progress task is not started until task task is finished.
You can create a background thread that runs in parallel with the task until it is finished and in there you can tell the UI thread to animate the dots once per second. Since the UI thread is NOT blocked (but only waiting for the task to finish), this works.
Example code:
string originalLblContent = (lblProgress.Content as string) ?? "";
bool taskStarted = false;
var progressThread = new Thread((ThreadStart)delegate
{
// this code will run in the background thread
string dots = "";
while(!taskStarted || !task.IsCompleted) {
if(dots.Length < 3) {
dots += ".";
} else {
dots = "";
}
// because we are in the background thread, we need to invoke the UI thread
// we can invoke it because your task is running asynchronously and NOT blocking the UI thread
Dispatcher.Invoke(() =>
{
lblProgress.Content = originalLblContent + dots;
});
Thread.Sleep(1000);
}
});
progressThread.Start();
taskStarted = true;
await task;
// the task is now finished, and the progressThread will also be after 1 second ...
Your approach is a little funky here. The await statements will prevent the method returning until each thread is finished. The await feature is not a completely asynchronous execution (why would it be? you have threads for that).
You need to re-think your approach to the problem. Fundamentally, you want to update the UI while another process is in progress. This calls for multithreading.
From Microsoft:
"Handling blocking operations in a graphical application can be difficult. We don’t want to call blocking methods from event handlers because the application will appear to freeze up. We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the UI thread because we can’t directly modify the GUI from our worker thread. We can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the UI thread. Eventually, these delegates will be executed with permission to modify UI elements.
In this example, we mimic a remote procedure call that retrieves a weather forecast. We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UI thread when we’re finished."
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model

Update ModalDialog progress bar from async await

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);
}
}

Sharing variables between page and background task

I have a pretty long task for my page, so I'm running it in a background task since I don't want it to block any postback events. Let's just assume this code looks something like this
Task task = new Task(() =>
{
for (;;)
{
Thread.Sleep(5000);
var x = (int)ViewState["progress"];
x++;
ViewState["progress"] = x;
}
});
task.Start();
As you can see, I'm storing the code in the ViewState. I have a progress bar on the page along with a timer that ticks every 5 seconds to perform a postback event. In this event, I'm updating the progress bar. The code looks something like this.
protected void Timer1_Tick(object sender, EventArgs e)
{
int perc = (int)ViewState["progress"];
//Progress bar code here
}
The progress bar wasn't updating so I tried debugging and for some reason, I'm getting entirely different values in both the cases. The timer's event gives me 0 all the time but it seems to be increasing in the loop. It's like there are two different instances of the ViewState
I've also tried locking the ViewState or using the global variable instead, but no luck.
What you have is a recipe for disaster. Thead.Sleep will suspend the thread which is very undesirable.
Disclaimer I do not recommend this technique!
You might consider using the slightly better
task = Task.Run(async () =>
{
for (var progress = 0; progress < 100; ++progress)
{
await Task.Delay(5000);
ViewState[nameof(progress)] = progress;
}
});
That said, it is still a recipe for disaster.

Using async/await: await returns too early

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

C#/WPF Using async and await but the UI thread is still blocked

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.

Categories

Resources