ReactiveUI 7.0 如何处理抛出异常时处理的 observable [英] ReactiveUI 7.0 how to handle with observables that are disposed when exception is thrown
问题描述
我真的开始挖掘这个 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:
- 处理命令代码中的异常,这样 ThrownExceptions 就不会真正被触发.
- 如果 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屋!