问题
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