2016-11-26 119 views
0

我真的开始挖掘这个rx的东西......基本上,我跟着this video一起学习了一些关于ReactiveUI的知识,然后开始使用它真的!ReactiveUI 7.0如何处理抛出异常时处置的observables

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

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属性。

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

看来这是打算的行为,但我们怎么能得到可观察的工作呢?我意识到我可以把它全部包装在try/catch中,并返回一些非例外的东西,但是,我在video(约39:03)看到,在他的情况下,searchtext在抛出异常后继续工作? (该vid的源代码是here)。

我也看到here东西约UserError,但现在标记为遗产。

回答

0

好的,我有一些工作,我虽然我会张贴它。有几个问题我不得不处理。其中一个事实是,我在我的命令异步任务代码(在后台线程中触发并因此引发异常)内部设置了我的IsError=false属性,另一个是在ThrownExceptions冒泡后如何重新订阅observable。还有,我发现工作方法2 /解决方法:

  1. 处理中命令代码异常,以便ThrownExceptions实际上从未被炒鱿鱼。
  2. 如果ThrownExceptions没有被触发,那么处置并重新订阅WhenAnyValue可观察值,以便继续前进。 (这需要在WhenAnyValue对象中保留一个变量)。

这里是整个视图模型代码似乎工作。警告:我自己是rx/rxui的新手,我不知道这是做这一切的最好方法!我正在成像,可能有更好的方法!

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); 
} 

} 
0

你可以在此使用interactions

public enum ErrorRecoveryOption 
{ 
    Retry, 
    Abort 
} 

public static class Interactions 
{ 
    public static readonly Interaction<Exception, ErrorRecoveryOption> Errors = new Interaction<Exception, ErrorRecoveryOption>(); 
} 

public class SomeViewModel : ReactiveObject 
{ 
    public async Task SomeMethodAsync() 
    { 
     while (true) 
     { 
      Exception failure = null; 

      try 
      { 
       DoSomethingThatMightFail(); 
      } 
      catch (Exception ex) 
      { 
       failure = ex; 
      } 

      if (failure == null) 
      { 
       break; 
      } 

      // this will throw if nothing handles the interaction 
      var recovery = await Interactions.Errors.Handle(failure); 

      if (recovery == ErrorRecoveryOption.Abort) 
      { 
       break; 
      } 
     } 
    } 
} 

public class RootView 
{ 
    public RootView() 
    { 
     Interactions.Errors.RegisterHandler(
      async interaction => 
      { 
       var action = await this.DisplayAlert(
        "Error", 
        "Something bad has happened. What do you want to do?", 
        "RETRY", 
        "ABORT"); 

       interaction.SetOutput(action ? ErrorRecoveryOption.Retry : ErrorRecoveryOption.Abort); 
      }); 
    } 
} 

看: https://docs.reactiveui.net/en/user-guide/interactions/index.html

相关问题