In my first post, we talked about the basic functions of the asynchronous task framework, we did a little bit of digging on the correlation between the event/observer pattern and the awaitables.
The main purpose of these posts, as I explained, was to create a more responsive and fluid UI. So in this post, I want to start by analyzing the UI progress elements for a Windows Store App, and move on to the backend implementation. I will be using the design guidelines for progress described at Guidelines for Progress Controls.
Indeterminate Tasks
Indeterminate tasks, as the name suggests, are the operations of which we cannot let the user know about the estimated completion time (i.e. Not that we don’t want to, we just simply cannot). These operations can be part of a whole process flow (e.g. if the application first needs to connect to a server, then download certain items, then…etc.), or it can be related to a specific element on the UI, or it can relate to the whole application.
Following the fast/fluid ideology, we should refrain from using implementations that would block the whole UI unless the task being processed is crucial for the usage of the application (e.g. we are loading data to show on a grid that the user is going to use to make a decision.)
For indeterminate tasks, we have two basic progress indicators at our disposal: Indeterminate Progress Ring and Indeterminate Progress Bar.
You can make use of these items either on a specific UI element or applying it to the whole screen either in the middle of the window (i.e. progress ring) or on top of the whole application (i.e. progress bar).
In our decision tree, first thing we need to decide on is if we want to have a blocking call (i.e. the process is an integral part of the application execution). In a blocking call, the only thing we can give to the user is “hey there is an operation going on, please wait to continue whatever you are trying to do”. It is not very nice of us, but it is what it is. We are doing our best to inform the user about the current execution state and giving the latest updates (i.e. in case of the multi-step workflow).
When the call is not blocking, meaning, the user can continue interacting with the UI while the process continues, developers have a little more options. In this scenario, we would just give subtle indications about the part of or the whole application that are involved in the process.
This is almost like starting a good old BackgroudWorker or a Thread and changing the view of the progress indicator when the operation is completed. The implementation in windows store apps would involve a simple async method implementation.
private async void MyAsynchronousTask(object sender, RoutedEventArgs e) { Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressRing.IsActive = true; }); // Here we are doing something with an awaitable task (in this case we are just // waiting for 10 secs, but this can be an actual implementation) await Task.Delay(new TimeSpan(0, 0, 10)); Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressRing.IsActive = false; }); }
If, in fact, we assign this function as an event handler to a button on the UI, it would make the progress ring (i.e. a progress ring element with the name x:Name=”ProgressRing”) active for 20 secs and then deactivate it.
NOTE: It is important to notice the use of the Dispatcher (i.e. current windows dispatcher) to execute the UI related functions. It is just like using the infamous InvokeRequired/Invoke group from good old days to marshal the UI thread.
Determinate Tasks
Determinate tasks are the ones that we have a clear idea about the current state of progress and we have an estimate for the user as for how much more left to do. We can think about it as a background worker (sorry for all the background worker analogies but it is the closest to the asynchronous implementation in Windows Store apps), which is constantly firing the ProgressChanged event, giving the chance to the developer to reflect this progress on the UI.
So as the diagram suggests, for the determinate processes, we can actually use a determinate progress bar. We can easily update the progress bar value on progress changed events. Using the example from the previous section.
Let’s create a determinate progress bar first:
<ProgressBar x:Name="ProgressBar" IsIndeterminate="False" Value="0"></ProgressBar>
Then we can go ahead and implement a quick demonstration function to update the progress bar.
private async void MyAsynchronousTaskWithProgress(object sender, RoutedEventArgs e) { Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressBar.Value = 10; }); await Task.Delay(new TimeSpan(0, 0, 4)); Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressBar.Value = 40; }); await Task.Delay(new TimeSpan(0, 0, 4)); Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressBar.Value = 70; }); await Task.Delay(new TimeSpan(0, 0, 4)); Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { ProgressBar.Value = 100; }); }
Using IProgress<T>
In the previous example we are using arbitrary values to update the progress, we can also have a look at a real life scenario. Let’s assume we want to download a file from web. For this example, we can make use of this handy module in windows store apps: BackgroundDownloader.
private async Task DownloadAsync(Uri source, StorageFile destinationFile) { BackgroundDownloader downloader = new BackgroundDownloader(); DownloadOperation download = downloader.CreateDownload(source, destinationFile); Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress); await download.StartAsync().AsTask(progressCallback); }
NOTE: You notice we are using the AsTask extension method here to convert the Windows Runtime Async operation to a Task. This is just for clarity. We could have easily used the IAsyncOperationWithProgress interface to achieve the same.
In our download operation, we are creating a progress handler that is accepting the parameter DownloadOperation. Now if we use the previous progress bar sample to update the UI with the download progress, we have a determinate progress sample in our hand.
private void DownloadProgress(DownloadOperation download) { double percent = 100; if (download.Progress.TotalBytesToReceive > 0) { percent = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive; } Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>; { ProgressBar.Value = percent; }); }
However, this still does not apply to a scenario where we want to execute a more complicated operation. Let’s assume I want to calculate, a bit of a cliché but still, n-th number of the Fibonacci sequence.
private IAsyncOperationWithProgress<int, int> CalculateFibonnaci(int index) { var previous = -1; var next = 1; return AsyncInfo.Run<int, int>((token, progress) => Task.Run(() => { for (int i = 0; i < index; i++) { int sum = next + previous; previous = next; next = sum; progress.Report(i/index); } return next; })); }
In this implementation, I am using the AsyncInfo factory to create the IAsyncOperationWithProgress. The reason for the Task.Run is actually only to create an asynchronous task to execute the for-loop for the calculation. Here, the progress reporting happen in each cycle; giving a percentage to the user about the current calculation process.
You can also have a look at my previous post Download/Send Request AsyncWithProgress with HttpClient for a progress implementation.
Using CancellationToken
In the workflow diagram, you can see that it is suggested to provide a way to cancel the operation if it is taking longer than 10 seconds. If this was a BackgroundWorker, one would simply use the Cancel method to start a cancellation and check for pending cancellations in the worker thread.
The use case here is pretty similar. In asynchronous operations, you have something called a CancellationTokenSource and a token that reflects any cancellation source events to the processing task (kind of a flare gun and the signaling flare).
So, we have to first modify the Fibonacci method to accept a CancellationToken. In the Fibonacci loop, then, we have to check if the cancellation is requested and decide to continue or abort the operation.
private IAsyncOperationWithProgress<int, int> CalculateFibonnaci(int index, CancellationToken cancelToken) { var previous = -1; var next = 1; return AsyncInfo.Run<int, int>((token, progress) => Task.Run(() => { if (cancelToken != CancellationToken.None) token = cancelToken; for (int i = 0; i < index; i++) { if (token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); } int sum = next + previous; previous = next; next = sum; progress.Report((i/index)*100); } return next; }, token)); }
So in each loop, we are checking in a cancellation is signaled, and if so, we are throwing an OperationCancelledException exception.
And using this async operation with progress and cancellation token would look like:
private async Task UseFibonacciMethod() { try { Action reportProgress = (progress) => Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { ProgressBar.Value = progress; }); m_CancellationSource = new CancellationTokenSource(); var token = m_CancellationSource.Token; var operationWithProgress = CalculateFibonnaci(800, token); operationWithProgress.Progress = (result, progress) => reportProgress(progress); // You can cancel it after a certain amount of time, which would trigger an OperationCancelledException // m_CancellationSource.CancelAfter(2000); return await operationWithProgress; } catch (OperationCanceledException ex) { // When the operation is cancelled by the cancellation source } catch (Exception ex) { // If anything goes wrong with fibonnaci (e.g. sum > int.Max) } return -1; }
Hope you enjoyed the post. Happy coding everyone.
P.S> You can find the complete progress workflow diagram here.