El comando async ReactiveUI 6 no se ejecuta en subproceso en segundo plano en la aplicación WPF
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; }
}
y la vista tiene un<Button Command="{Binding GetApplicationExtensions}"></Button>
.
implementación deGetApplicationExtensions
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;
}
De todo lo que he leído sobre ReactiveUI y todos los ejemplos que he visto (aunque hay muy pocos para las nuevas versiones 6.0+), esto debería hacer mi llamada asíncrona (que realiza una solicitud HTTP asíncrona a través deHttpClient
) ejecutar en un hilo de fondo y actualizar unListBox
en mi opinión, cuando se devuelven los resultados. Sin embargo, este no es el caso: la interfaz de usuario se bloquea durante la llamada asincrónica. ¿Qué estoy haciendo mal?
Si envolví mi llamada HTTP en unTask
entonces todo funcionó según lo previsto: la interfaz de usuario no colgó en absoluto. Entonces, la nueva implementación de mi llamada de servicio es esta:
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;
}
Además, con este cambio en mi llamada al servicio asíncrono, podría eliminar elObserveOn
ySubscribeOn
de miReactiveCommand
como @PaulBetts sugirió. Así que miReactiveCommand
La implementación en el constructor de mi modelo de vista se convirtió en esto:
GetApplicationExtensions =
ReactiveCommand.CreateAsyncTask(x => _schemaService.GetApplicationExtensions()); // returns a Task<IEnumerable<string>>
GetApplicationExtensions
.Subscribe(p =>
{
using (_appExtensions.SuppressChangeNotifications())
{
_appExtensions.Clear();
_appExtensions.AddRange(p);
}
});