4

ViewModel

public class MyViewModel:ReactiveObject, IRoutableViewModel{
        private ReactiveList<string> _appExtensions; 

        public MyViewModel(IScreen screen){
            HostScreen = screen;
            AppExtensions = new ReactiveList<string>();

            GetApplicationExtensions =
                ReactiveCommand.CreateAsyncTask(x => _schemaService.GetApplicationExtensions()); // returns a Task<IEnumerable<string>>

            GetApplicationExtensions
                .ObserveOn(RxApp.MainThreadScheduler)
                .SubscribeOn(RxApp.TaskpoolScheduler)
                .Subscribe(p =>
                {
                    using (_appExtensions.SuppressChangeNotifications())
                    {
                        _appExtensions.Clear();
                        _appExtensions.AddRange(p);
                    }
                });

            GetApplicationExtensions.ThrownExceptions.Subscribe(
                ex => Console.WriteLine("Error during fetching of application extensions! Err: {0}", ex.Message));
        }

        // bound to a ListBox 
        public ReactiveList<string> AppExtensions
        {
            get { return _appExtensions; }
            set { this.RaiseAndSetIfChanged(ref _appExtensions, value); }
        } 

       public ReactiveCommand<IEnumerable<string>> GetApplicationExtensions { get; protected set; }
}

and the View has a <Button Command="{Binding GetApplicationExtensions}"></Button>.

implentation of GetApplicationExtensions

    public async Task<IEnumerable<string>> GetApplicationExtensions()
    {
        IEnumerable<string> extensions = null;

        using (var client = new HttpClient())
        {
            client.BaseAddress = BaseAddress;
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);

            var response = await client.GetAsync("applications");

            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                extensions = JsonConvert.DeserializeObject<IEnumerable<string>>(json);

            }

        }
        return extensions;
    }

From everything I've read about ReactiveUI and all the examples I've seen (althought there are extremely few for the new 6.0+ versions), this should make my async call (which makes an async HTTP request via HttpClient) run on a background thread and update a ListBox in my view when the results are returned from it. However, this is not the case - the UI gets locked up for the duration of the async call. What am I doing wrong?

UPDATE

If I wrapped my HTTP call in a Task then everything worked as intended - the UI did not hang up at all. So the new implementation of my service call is this:

    public Task<IEnumerable<string>> GetApplicationExtensions()
    {
        var extensionsTask = Task.Factory.StartNew(async () =>
        {
             IEnumerable<string> extensions = null;

             using (var client = new HttpClient())
             {
                 client.BaseAddress = BaseAddress;
                 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);

                 var response = await client.GetAsync("applications");

                 if (response.IsSuccessStatusCode)
                 {
                     var json = await response.Content.ReadAsStringAsync();
                     extensions = JsonConvert.DeserializeObject<IEnumerable<string>>(json);

                 }

             }
             return extensions;
        }

        return extensionsTask.Result;
    }

Also, with this change to my async service call, I could remove the ObserveOn and SubscribeOn from my ReactiveCommand like @PaulBetts suggessted. So my ReactiveCommand implementation in my view model's constructor became this:

            GetApplicationExtensions =
                ReactiveCommand.CreateAsyncTask(x => _schemaService.GetApplicationExtensions()); // returns a Task<IEnumerable<string>>

            GetApplicationExtensions
                .Subscribe(p =>
                {
                    using (_appExtensions.SuppressChangeNotifications())
                    {
                        _appExtensions.Clear();
                        _appExtensions.AddRange(p);
                    }
                });
Community
  • 1
  • 1
rwisch45
  • 3,692
  • 2
  • 25
  • 36
  • This might help http://stackoverflow.com/questions/25123992/reactiveui-cant-get-code-to-run-on-background-thread or http://stackoverflow.com/questions/24569877/how-to-make-reactivecommand-async – kenny Aug 10 '14 at 02:08
  • @kenny Thanks, but I've checked those out already. The first does not actually do any async work, and the second is not really different from what I'm already doing. – rwisch45 Aug 10 '14 at 16:05

2 Answers2

1

Can you show the implementation of _schemaService.GetApplicationExtensions()?

Depending on how it is implemented it might not actually be on another thread. Arguably, ReactiveCommand should guarantee that even async operations that accidentally burn CPU before running an async op are forced onto background threads, but efficiency is trumping defensive programming in this case.

You shouldn't need either SubscribeOn or ObserveOn, ReactiveCommand already guarantees that values will be returned on the UI thread. Otherwise, this code is looking good!

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I updated the question with the implementation of `_schemaService.GetApplicationExtensions()` – rwisch45 Aug 10 '14 at 19:23
  • Hm, that looks fine. Is it possible that your ListBox is loading a lot of items / its DataTemplate is complicated? It could be that the operation is completing async, but the UI has too much work to do. Try to break in while its hung and see what it's doing, make sure to uncheck "Just My Code" – Ana Betts Aug 10 '14 at 19:25
  • The UI is extremely simple right now - it only has the `ListBox` with no `DataTemplate` and the `Button` that has the command bound to it. – rwisch45 Aug 10 '14 at 19:29
  • Can you file a bug on http://github.com/reactiveui/reactiveui and include a demo project? – Ana Betts Aug 10 '14 at 21:01
  • Paul, I began to do just that; and in creating the demo project I arrived at the fix that I posted as an update in my original question. Is this the correct approach? – rwisch45 Aug 11 '14 at 15:24
  • I have the same problem, and I also switched to Task-based solution (temporarily, I hope). Not sure about testing though. Say, if I have a ReactiveCommand based on an async task that runs for 1 s, how can I use TestScheduler to test it? – glebd Aug 11 '14 at 18:07
  • If you are using Tasks, you cannot use TestScheduler. Usually though, you don't *need* TestScheduler, you can just await ExecuteAsync – Ana Betts Nov 15 '14 at 22:41
-1

Change

 GetApplicationExtensions
            .ObserveOn(RxApp.MainThreadScheduler)
            .SubscribeOn(RxApp.TaskpoolScheduler)

to

 GetApplicationExtensions
            .SubscribeOn(RxApp.TaskpoolScheduler)
            .ObserveOn(RxApp.MainThreadScheduler)

ObserveOn should be after SubscribeOn

yo chauhan
  • 12,079
  • 4
  • 39
  • 58
  • I did that, but nothing changed - the UI still hangs up until the async call is completed – rwisch45 Aug 09 '14 at 22:21
  • Indeed, the order of `ObserveOn` and `SubscribeOn` will make no difference. See http://stackoverflow.com/questions/20451939/observeon-and-subscribeon-where-the-work-is-being-done/20452901#20452901 for an in depth explanation. – James World Mar 06 '15 at 12:46