ReactiveUI 7.0 如何处理抛出异常时处理的 observable [英] ReactiveUI 7.0 how to handle with observables that are disposed when exception is thrown

查看:68
本文介绍了ReactiveUI 7.0 如何处理抛出异常时处理的 observable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我真的开始挖掘这个 rx 的东西...基本上,我正在关注 这个视频 只是为了在我开始真正使用它之前自学更多有关 ReactiveUI 的知识!

I'm really starting to dig this rx thing... Basically, I am following along with this video just to teach myself more about ReactiveUI before I start using it for real!

当我们使用 WhenAnyValue 执行限制搜索时,我试图创造一种情况.而且,如果搜索函数抛出异常,我想在视图模型上设置一个名为 IsError 的属性(这样我就可以显示 X 或其他东西).这是我工作的 ViewModel 的重要部分:

I am trying to create a situation when we use WhenAnyValue to perform a throttled search-as-you-type. And, if the search function throws an exception, I want to set a property on the view model called IsError (so I can show an X or something). This the important parts of the ViewModel I have working:

public ReactiveCommand<string, IEnumerable<DictItem>> SearchCmmand;

...  in vm constructor:

//create our command async from task. executes on worker thread
SearchCmmand = ReactiveCommand.CreateFromTask<string, IEnumerable<DicItem>>(async x => {
    this.IsError = false;
    //this may throw an exception:
    return await GetFilteredAsync(this.SearchText); 
  });

//SearchCommand is subscribable.  
//set the Filtered Items property. executes on main thread
SearchCmmand.Subscribe(filteredItems => {
  this.FilteredItems = filteredItems;
});

//any unhandled exceptions that are thown in SearchCommand will bubble up through the ThrownExceptions observable    
SearchCmmand.ThrownExceptions.Subscribe(ex=> {
  this.IsError = true;
  //but after this, then the WhenAnyValue no longer continues to work.
  //how to get it back?
});

//invoke the command when SearchText changes
this.WhenAnyValue(v => v.SearchText)
  .Throttle(TimeSpan.FromMilliseconds(500))
  .InvokeCommand(SearchCmmand);

这是有效的.当我的 GetFilteredAsync 抛出异常时,SearchCmmand.ThrownExceptions 被调用,我可以设置我的 IsError 属性.

And this works. When my GetFilteredAsync throws an exception, the SearchCmmand.ThrownExceptions gets called and I can set my IsError property.

然而,当 SearchCmmand.ThrownExceptions 第一次发生时,this.WhenAnyValue(v => v.SearchText) 停止工作.我可以看到它被处理掉了.对 SearchText 的后续更改不会调用该命令.(尽管如果我绑定了一个按钮,该命令仍然有效)

However, when SearchCmmand.ThrownExceptions happens the first time, the this.WhenAnyValue(v => v.SearchText) stops working. I can see that it gets disposed. Subsequent changes to SearchText do not invoke the command. (though the command still works if I have a button bound to it)

这似乎是预期的行为,但是我们怎样才能让 observable 再次工作呢?我意识到我可以将它全部包装在 try/catch 中并返回一些不是例外的东西,但是,我在 video(大约 39:03)在他的情况下搜索文本在抛出异常后继续工作?(该视频的源代码是这里).

It seems this is intended behaviour, but how can we get the observable working again? I realize that I could just wrap it all in a try/catch and return something that is not an exception, however, I see in the video (around 39:03) that in his case the searchtext continues to work after the exception is thrown? (the source code for that vid is here).

我还看到这里关于UserError的一些信息,但就是这样现在标记为旧版.

i also see here something about UserError, but that's now marked as Legacy.

推荐答案

好的,所以我有一些工作,我想我会发布它.我必须处理几个问题.一个是我在我的命令异步任务代码中设置了我的 IsError=false 属性(它在后台线程上触发并因此引发异常),另一个是处理如何重新在 ThownExceptions 冒泡后订阅 observable.我发现有两种方法/解决方法有效:

Ok so I have got something working, i though i'd post it. There were a couple issues i had to deal with. One was the fact that i was setting my IsError=false property inside my command async task code, (which fires on the background thread and hence throws an exception) and the other was dealing with how to re-subscribe the observable after the ThrownExceptions bubbles up. There are 2 approaches/workarounds that I found worked:

  1. 处理命令代码中的异常,这样 ThrownExceptions 就不会真正被触发.
  2. 如果 ThrownExceptions 确实被触发,则处理并重新订阅 WhenAnyValue observable,使其继续运行.(这需要为 WhenAnyValue 对象保留一个变量.)

这里是似乎可以工作的整个视图模型代码.警告:我自己是 rx/rxui 的新手,我不知道这是否是完成所有这些的最佳方式!我在想象可能有更好的方法!

here is the entire view model code that seems to work. WARNING: being new to rx/rxui myself, i don't know if this is the best way to do all of this! I'm imaginging there could be better ways!

public class SearchViewModel1 : ReactiveObject {

IEnumerable<DictItem> itemList; //holds the master items. used like a repo (just for demo, i'd use a separate repo or service class for real)

ObservableAsPropertyHelper<bool> _isBusy;
public bool IsBusy {
  get { return _isBusy.Value; }
}

bool _isError;
public bool IsError {
  get { return _isError; }
  set { this.RaiseAndSetIfChanged(ref _isError, value); }
}

//the filtered items property that we want to bind our list to
IEnumerable<DictItem> _filteredItems;
public IEnumerable<DictItem> FilteredItems {
  get { return _filteredItems; }
  set { this.RaiseAndSetIfChanged(ref _filteredItems, value); }
}

//the search text, this will be bound
//and this viewmodel will respond to changes to the property. 
string _searchText;
public string SearchText {
  get { return _searchText; }
  set { this.RaiseAndSetIfChanged(ref _searchText, value); }
}

//this is the reacive command that takes a string as a parameter, 
public ReactiveCommand<string, IEnumerable<DictItem>> SearchCmmand { get; set; }

//a reference to our observable in case we lose it and need to resubscribe
IDisposable whenAnySearchText;


//convenience method to set the IsError property. can be called by a worker thread
void SetIsErrorFromWorkerThread(bool isError) {
  Observable.Return(isError)
    .SubscribeOn(RxApp.MainThreadScheduler)
    .Subscribe(b => this.IsError = b);
}


//constructor is where we wire it all up
public SearchViewModel1(IEnumerable<DictItem> itemList) {

  this.itemList = itemList;

  FilteredItems = itemList;

  //this observable keeps track of when SearchText is blank.
  var searchTextHasValue = this.WhenAnyValue(x => x.SearchText)
    .Select(x => !string.IsNullOrWhiteSpace(x));

  //create our command async from task.
  //it will only actually fire if searchTextHasValue is true.
  SearchCmmand = ReactiveCommand.CreateFromTask<string, IEnumerable<DictItem>>(async x => {
        SetIsErrorFromWorkerThread(false);
        //first we'll try to capture any exceptions here, so we don't lose the observable.
        try {
          return await GetFilteredAsync(SearchText, itemList);
        } catch (Exception ex) {
          SetIsErrorFromWorkerThread(true);
          return Enumerable.Empty<DictItem>();
        }
    },
    searchTextHasValue);

  //searchCommand is subscribable.  set the Filtered Items property synchronous here on main thread
  SearchCmmand.Subscribe(filteredItems => {
    FilteredItems = filteredItems;
  });

  //any unhandled exceptions that are thown in SearchCommand will bubble up through the ThrownExceptions observable
  SearchCmmand.ThrownExceptions.Subscribe(ex => {
    //note: because we are handling exceptions in the command code,
    //this should be a very last-case and never-happen scenario.  
    //but we seem to be able to recover by re-subscribing the observable
    IsError = true;
    //we have lost the subscription.  so set it again?
    //is this even a good idea?
    whenAnySearchText.Dispose();
    whenAnySearchText = this.WhenAnyValue(v => v.SearchText)
      .Throttle(TimeSpan.FromMilliseconds(500))
      .InvokeCommand(SearchCmmand);
  });

  //the IsBusy can just be wired from the Command observable stream
  _isBusy = SearchCmmand.IsExecuting.ToProperty(this, vm => vm.IsBusy);

  //bind our whenAnySearchText 
  whenAnySearchText = this.WhenAnyValue(v => v.SearchText)
    .Throttle(TimeSpan.FromMilliseconds(500))
    .InvokeCommand(SearchCmmand);
}

 //the task to run the search/filter
 async Task<IEnumerable<DictItem>> GetFilteredAsync(string filterText, IEnumerable<DictItem> items) {
   await Task.Delay(1000);
   if (filterText.Length == 5) {
     throw new InvalidOperationException("You cannot search 5 characters!  Why? No reason, it's contrived.");
   }
   return items.Where(x => x.Name.IndexOf(filterText, StringComparison.OrdinalIgnoreCase) >= 0);
 }

}

这篇关于ReactiveUI 7.0 如何处理抛出异常时处理的 observable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆