3

I'm using a ProgressBar with binding to show the progress when receiving a file from a remote device.

<ProgressBar Width="500" Height="50" Value="{Binding ProgressFileReceive}"/>

ProgressFileReceive is a property (double) in my View Model which has the percentage completion. So to update the Progress Bar, I add to this number.

The problem is I have the file transfer method in a different async method, and so to access this property I must use the following code :

await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
   () =>
   {
       // do something on the UI thread
       ProgressFileReceive = (double)i / fileSize * 100;
   });

This works but makes the whole process extremely slow, since at each iteration (there are over a thousand loops since I read byte-by-byte) the method has to use the dispatcher to update the UI. It takes several times longer to receive the whole file, than it would take if I was not updating the UI.

How can I do this more efficiently so as to speed up the process ?

5
  • One simple way: only update the progress bar every X iterations, where X is large enough not to slow down processing too much, but not so large as to make the progress bar jaggy. Commented Jul 7, 2016 at 14:07
  • @Aniruddha Varma, you don't need to marshal the binded data to UI thread manually, because you're using binding. WPF takes care about it. Commented Jul 7, 2016 at 14:25
  • @stuartd Thanks. Yes I'll put a counter in, that should work as long as it appears more or less smooth. Commented Jul 7, 2016 at 14:25
  • @ArtavazdBalayan The set accessor raises an exception in the RaisePropertyChanged othwerwise. It's a windows store (metro) app. Commented Jul 7, 2016 at 14:27
  • @Aniruddha Varma, omg, thanks, I didn't know about it Commented Jul 7, 2016 at 14:31

1 Answer 1

3

The problem is I have the file transfer method in a different async method

That doesn't necessarily follow. You shouldn't need to use CoreDispatcher explicitly. Asynchronous methods resume on the UI thread by default.


For progress reporting, you should use IProgress<T>. You can use it with a structure to report progress, as such:

public struct ProgressReport
{
  public double Progress { get; set; }
  public double FileSize { get; set; }
}

async Task FileTransferAsync(IProgress<ProgressReport> progress)
{
  ...
  if (progress != null)
  {
    progress.Report(new ProgressReport
    {
      Progress = (double)i,
      FileSize = fileSize
    });
  }
  ...
}

Then you can consume it with an IProgress<T> implementation. Since you need UI throttling, you can use one that I wrote that has built-in throttling:

using (var progress = ObservableProgress<ProgressReport>.CreateForUi(value =>
    {
        ProgressFileReceive = (double)value.Progress / value.FileSize * 100;
    }))
{
    await FileTransferAsync(progress);
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you. I tried it out but I got a COMException - The application called an interface that was marshalled for a different thread inside the ProgressFileReceive's set accessor in the RaisePropertyChanged() method.
@AniruddhaVarma: You have to construct the ObservableProgress on the UI thread.
I see. But the task must start from within the scope of an async method, so I wrapped your second piece of code inside a CoreWindow.Dispatcher to run it from within the UI thread. This throws no errors, but the progress bar is updated only at the end of the task, when it directly jumps to 100%.
@AniruddhaVarma: You should create the ObservableProgress before moving to a background thread. You shouldn't need to use a dispatcher at all.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.