ReactiveUI 6 Async Command Not Running on Backgrou

2019-02-18 01:16发布

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);
                    }
                });

2条回答
做自己的国王
2楼-- · 2019-02-18 01:37

Change

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

to

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

ObserveOn should be after SubscribeOn

查看更多
Deceive 欺骗
3楼-- · 2019-02-18 01:51

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!

查看更多
登录 后发表回答