【问题标题】:Cancellation of async Task in ReactiveUI ViewModel (ReactiveObject)取消 ReactiveUI ViewModel (ReactiveObject) 中的异步任务
【发布时间】:2014-06-08 14:38:53
【问题描述】:

我目前正在试验 ReactiveUI (5.5.1),并且我创建了一个 ViewModel(ReactiveObject 的子类),它可以作为 autocomplete 用于位置搜索(改编自mikebluestein/ReactiveUIDemo on github)。每次查询文本更改时,都会调用一个 REST 服务,该服务会为提交的查询返回匹配的位置。

问题:正如您在下面的代码中看到的,DoSearchAsync(string query, CancellationToken cancellationToken) 是可取消的,但是,我不确定如何(以及在​​代码中的何处)实际取消任何搜索 -因此使用CancellationToken.None atm。在这个特定的用例中取消请求似乎有点过头了,但我想知道在这个响应式 UI/异步任务场景中如何启用取消。

代码:

public class ReactiveLocationSearchViewModel : ReactiveObject {

readonly ReactiveCommand searchCommand = new ReactiveCommand();

public ReactiveCommand SearchCommand { get { return searchCommand; } }

string query;

public string Query
{
    get { return query; }
    set { this.RaiseAndSetIfChanged(ref query, value); }
}

public ReactiveList<Location> ReactiveData { get; protected set; }

public ReactiveLocationSearchViewModel()
{
    ReactiveData = new ReactiveList<Location>();
    var results = searchCommand.RegisterAsyncTask<List<Location>>(async _ => await DoSearchAsync(query, CancellationToken.None));

    this.ObservableForProperty<ReactiveLocationSearchViewModel, string>("Query")
        .Throttle(new TimeSpan(700))
        .Select(x => x.Value).DistinctUntilChanged()
        .Where(x => !String.IsNullOrWhiteSpace(x))
        .Subscribe(searchCommand.Execute);

    results.Subscribe(list =>
    {
        ReactiveData.Clear();
        ReactiveData.AddRange(list);
    });
}

private async Task<List<Location>> DoSearchAsync(string query, CancellationToken cancellationToken)
{
    // create default empty list
    var locations = new List<Location>();

    // only search if query is not empty
    if (!string.IsNullOrEmpty(query))
    {
        ILocationService service = ServiceContainer.Resolve<ILocationService>();
        locations = await service.GetLocationsAsync(query, cancellationToken);
    }

    return locations;
}

}

【问题讨论】:

    标签: c# mvvm reactiveui cancellation


    【解决方案1】:

    RxUI 5.x 没有这个内置,但是很容易伪造:

    var results = searchCommand.RegisterAsync<List<Location>>(
        _ => Observable.StartAsync(ct => DoSearchAsync(query, ct)));
    

    在 RxUI v6 中,这是内置的:

    searchCommand = ReactiveCommand.CreateAsyncTask(
        (_, ct) => DoSearchAsync(query, ct));
    

    【讨论】:

    • 哇,你真的在​​照顾你的宝宝 :-) 对于读者:在将 searchCommand 从 ReactiveCommand 类型更改为 ReactiveUI.Legacy.ReactiveAsyncCommand 后完美运行。看起来这将在 v6 中发生变化(旧版......)无论如何,谢谢 Paul!
    • 它也应该适用于 v5 命令,我只是名称错误
    • @paul-betts 在你上面的例子中,取消令牌来自哪里,即它的CancellationTokenSource 在哪里?当按下按钮时,如何通过其令牌源的 Cancel() 手动取消它?
    • 取消令牌来自于取消订阅 ExecuteAsync observable,或者通过显式将令牌传递给 ExecuteAsyncTask
    • @PaulBetts 这究竟是如何工作的?取消订阅会永久断开已订阅的方法,要求您在每次取消后重新订阅,并且取消令牌源是一次性对象,因此您必须在每次取消后重建整个命令。我真的不明白这应该如何工作。
    【解决方案2】:

    ReactiveUI 9.x 更新:

    定义属性

    // using ReactiveUI.Fody
    [Reactive]
    public ReactiveCommand<TQuery, IEnumerable<TResult> SearchCommand { get; set; }
    
    private CompositeDisposable Disposables { get; } = new CompositeDisposable();
    

    在构造函数中

    // hook up the command
    var searchCommand = ReactiveCommand.CreateFromTask<TQuery, IEnumerable<TResult>>((query, ct) => DoSearchAsync(query, ct)));
    SearchCommand = searchCommand;
    
    // don't forget to subscribe and dispose
    searchCommand
        .Subscribe(result => Result = result)
        .DisposeWith(Disposables);
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多