How to properly cancel a Task on ViewModel-deactivation with ReactiveUI?

 ̄綄美尐妖づ 提交于 2021-02-10 12:44:59

问题


In my MVVM application, when a ViewModel gets activated, a Task gets started that establishes a network connection and could take some time to complete. This Task is cancalable:

private async Task ConnectAsync(CancellationToken cancellationToken = default)
{
    ...
}

I'm using IActivatableViewModel to start it on ViewModel-activation like that:

// Constructor:
public SomeViewModel(...)
{
    this.WhenActivated(disposable => {
        Observable.StartAsync(ConnectAsync);
    });
}

Now what is the recommended method to cancel this long-running Task when the ViewModel gets deactivated before the Task completes?

I came up with this:

this.WhenActivated(disposable => {
    Observable.StartAsync(ConnectAsync).Subscribe().DisposeWith(disposable);
});

Is this the right solution or is there a better one?

Thank you in advance!


回答1:


Yeah, the code like you show in your code snippet looks good. However, probably worth moving the ConnectAsync method call to a ReactiveCommand<TInput, TOutput> (docs). If you do this, you get such perks as the ability to subscribe to ThrownExceptions and IsExecuting observables, and then display some loading indicators or error messages to keep your users informed about what the app is doing. Also, following the pattern described here, you can cancel that ReactiveCommand<TInput, TOutput> via another command or event. Cancelation via an event would look like this:

// ViewModel.cs
Cancel = ReactiveCommand.Create(() => { });
Connect = ReactiveCommand
    .CreateFromObservable(
        () => Observable
            .StartAsync(ConnectAsync)
            .TakeUntil(Cancel));

// View.xaml.cs
this.WhenActivated(disposable => {
    this.Events() // Launch the long-running operation
        .Loaded
        .Select(args => Unit.Default)
        .InvokeCommand(ViewModel, x => x.Connect)
        .DisposeWith(disposable);
    this.Events() // Stop that long-running operation
        .Unloaded
        .Select(args => Unit.Default)
        .InvokeCommand(ViewModel, x => x.Cancel)
        .DisposeWith(disposable);
});

Here, I assume ConnectAsync is a method accepting a cancelation token and returning a Task. In order to enable the this.Events() magic, you need to either use Pharmacist, or to install one of the ReactiveUI.Events packages. But anyway, your option looks good as well if you want to rely on WhenActivated, don't need ThrownExceptions, IsExecuting etc. If you'd like to use commands and rely on WhenActivated, then modify the View.xaml.cs code:

// View.xaml.cs
this.WhenActivated(disposable => {
    Connect.Execute().Subscribe();
    Disposable
        .Create(() => Cancel.Execute().Subscribe())
        .DisposeWith(disposable);
});

We aren't disposing of the subscriptions returned by Execute() because they'll get disposed anyway when the commands complete their execution. Hope this helps! ✨



来源:https://stackoverflow.com/questions/64124395/how-to-properly-cancel-a-task-on-viewmodel-deactivation-with-reactiveui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!