1

This code, called from the UI thread:

Jobs.Current.ShowProgress(_ =>
    {
        if (Jobs.Current.Duration == 0 && this.progressBarMarkJob.Value >= this.progressBarMarkJob.Maximum - this.progressBarMarkJob.Step / 2)
        {
            this.progressBarMarkJob.Step /= 2;
        }
        this.progressBarMarkJob.PerformStep();
    }, () =>
    {
        stopwatch.Stop();
        var elapsed = string.Format(stopwatch.Elapsed.TotalHours >= 1 ? @"{0:hh\:mm\:SS}" : @"{0:mm\:SS}", stopwatch.Elapsed);
        this.labelMarkTime.Text = string.Format("Elapsed{0:8}", elapsed);
        this.labelMarkTime.Visible = true;
        Jobs.Current.Duration = (uint)stopwatch.Elapsed.TotalSeconds;
        this.progressBarMarkJob.Value = this.progressBarMarkJob.Maximum;
    });

where ShowProgress does:

public void ShowProgress(Action<long> notify, Action terminate)
{
    this.Progress = Observable.Timer(ProgressInterval, ProgressInterval).Finally(terminate).Subscribe(notify);
}

blocks the UI thread completely, making it unresponsive.

If I insert .SubscribeOn(Scheduler.CurrentThread) before the Subscribe() call, it no longer blocks the UI thread. But then I get cross-thread exceptions, because the messages are not passed to the UI on the correct thread.

Is there a way to make that work so that I can get updates from the timer - leaving the UI responsive - that post back to the UI thread?

shipr
  • 2,809
  • 1
  • 24
  • 32
  • Obviously, you subscribe on a different thread than the UI, then martial your calls to update the progress bar onto the UI thread via its Dispatcher. You have all the info you need, not sure why you are having a problem with this. Background => UI call martialing is as old as the hills, and there's plenty of questions covering the subject here. –  Jan 27 '14 at 21:21
  • Thank you Will, I appreciate it, but I am not finding those answers, even after much search, and it is not at all obvious to me. How about a few pointers? In particular, I see that the documentation tells me to use Dispatcher, but when writing the code, the compiler flags an error when I try that option. – shipr Jan 27 '14 at 21:25

1 Answers1

3

You need to add a call to ObserveOn(). If you use nuget package Rx-Xaml you can leverage ObserveOnDispatcher():

this.Progress = Observable.Interval(ProgressInterval)
                          .ObserveOnDispatcher()
                          .Finally(terminate)
                          .Subscribe(notify);

See my answer here to understand the difference between ObserveOn and SubscribeOn. Also, you don't supply the code for PerformStep() - I hope that it is fast and/or non-blocking.

I also replaced Timer with Interval as it saves you an argument.

Finally, presumably you are planning to dispose the subscription handle (this.Progress) when the job completes?

Community
  • 1
  • 1
James World
  • 29,019
  • 9
  • 86
  • 120
  • Thanks, @James, very helpful, especially the contrast between SubscribeOn and ObserveOn. I like the improvement of using Interval also. The code for PerformStep is that supplied by ProgressBar; and yes, I Dispose of the timer elsewhere, when the job completes. Now, however, I get the error: Could not load file or assembly 'System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) – shipr Jan 27 '14 at 23:03
  • Sounds like you are mixing incompatible versions of the Rx dlls. Does somewhere else in your solution make a version specific reference to Reactive.Core? The latest version is 2.2. – James World Jan 28 '14 at 00:13
  • Yes, I was referencing an outdated version of Reactive.Windows.Forms. Fixed! Thanks again. All working now. – shipr Jan 28 '14 at 18:32