shawnco67
shawnco67

Reputation: 11

ReactiveUI: When using ReactiveCommand.CreateFromObservable() the UI does not update until the command completes

I'm using ReactiveUI 7.0 with WPF and .NET 4.5.2.

I'm trying to create a ReactiveCommand from an Observable. The code DOES work, however, the UI doesn't update until the command is completed. I have a progress bar and a progress window that I want to update as the command runs. In addition, the UI is unresponsive while the ReactiveCommand is executing (I can't click on a cancel button or anything else for that matter). I'm hoping it's something I am overlooking and obvious to somebody smarter than me. Or maybe I'm just doing it wrong.

Thanks for looking.

Here is the relevant code:

My ViewModel declaration:

public ReactiveCommand<Unit, string> PerformSelectedOperationCommand { get; set; }

private StringBuilder sb;

Within my ViewModel constructor:

PerformSelectedOperationCommand = ReactiveCommand.CreateFromObservable(PerformOperationObservable,
            this.WhenAnyValue(x => x.SelectedPath, x => x.TotalFilesSelected,
                (x, y) => x != null && y > 0));

    // listen to the messages and append to output
    PerformSelectedOperationCommand.Subscribe(s =>
    {
        sb.AppendLine(s);
        ProgressWindowOutput = sb.ToString();
    });

And here is the Observable contained in my ViewModel which runs when clicking on the Go button (Notice it's modifying properties of my ViewModel):

private IObservable<string> PerformOperationObservable()
    {
        sb.Clear();
        return Observable.Create<string>((o) =>
        {
            using (cts = new CancellationTokenSource())
            {
                // this creates a copy of the file list which will keep us safe
                // if the user is clicking around on the UI and changing the list.
                var selectedFiles = FileList.Where(x => x.IsChecked).ToArray();
                int total = selectedFiles.Length;
                int count = 0;
                foreach (var file in selectedFiles)
                {
                    ProgressBarMessage = $"Processing {count + 1} of {total}";
                    o.OnNext($"Processing file {file.Name}");
                    SelectedFileOperation.PerformOperation(file.FullPath);
                    count++;
                    PercentComplete = (int)(((double)count / total) * 100);
                    if (cts.IsCancellationRequested)
                    {
                        PercentComplete = 0;
                        ProgressBarMessage = string.Empty;
                        break;
                    }
                }
                ProgressBarMessage = string.Empty;
            }
            o.OnCompleted();
            return Disposable.Empty;
        });
    }

Upvotes: 1

Views: 2140

Answers (1)

Jon G St&#248;dle
Jon G St&#248;dle

Reputation: 3904

Observable are inherently single threaded, you need to specify where to do the work. You could do this, I believe:

PerformSelectedOperationCommand 
    = ReactiveCommand.CreateFromObservable(
        PerformOperationObservable.SubscribeOn(RxApp.TaskPoolScheduler), // Move subscription to task pool
            this.WhenAnyValue(
                x => x.SelectedPath, 
                x => x.TotalFilesSelected,
                (x, y) => x != null && y > 0
            )
      );

// listen to the messages and append to output
PerformSelectedOperationCommand
    .ObserveOnDispather() // Return to UI thread
    .Subscribe(s =>
    {
        sb.AppendLine(s);
        ProgressWindowOutput = sb.ToString();
    });

You explicitly subscribe to the observable on the task pool, moving the work of the UI thread. Right before using the output, you return to the UI thread to be able to do updates on the UI.

Upvotes: 2

Related Questions