简体   繁体   English

RxJava:如何重置长时间运行的热可观察链?

[英]RxJava: How can I reset a long running hot observable chain?

For my app's search feature, I have a hot observable chain that does the following. 对于我的应用程序的搜索功能,我有一个热门的可观察链,该链可进行以下工作。

  1. Accepts user input string into an EditText (a TextChangedEvent ) (on mainThread ) 将用户输入的字符串mainThread EditTextTextChangedEvent )中(在mainThread
  2. Debounce 300ms (on computation thread) 防抖动300ms(在computation线程上)
  3. Show loading spinner ( mainThread ) 显示加载微调器( mainThread
  4. Query the SQL db with that string (this query can take anywhere from 100ms to 2000ms) (on Schedulers.io() ) 使用该字符串查询SQL db(此查询可能需要100ms到2000ms的任何时间)(在Schedulers.io()
  5. Display results to the user ( mainThread ) 向用户显示结果( mainThread

Because step 3 is so variable in length, a race condition occurs where less recent search results are displayed over more recent results (sometimes). 由于步骤3的长度是可变的,因此会发生竞态条件,其中显示的是较新的搜索结果而不是较新的结果(有时)。 Let's say a user wants to type chicken , but because of weird typing speeds, the first part of the word is emitted before the whole term: 假设用户想输入chicken ,但是由于输入速度异常,该单词的第一部分在整个术语之前发出:

  • a search for chick is sent out first, followed by chicken . 首先发出对chick的搜索,然后是chicken
  • say chick takes 1500ms to execute while chicken takes 300ms to execute. 比如说chick需要1500ms 300ms执行,而chicken需要300ms执行。
  • this causes the chick search results to incorrectly display for the search term chicken . 这会导致chick搜索结果针对搜索词“ chicken显示不正确。 This is because the chicken search completed first (took only 300ms), followed by the chick search (1500ms). 这是因为先完成chicken搜索(仅花费300毫秒),然后完成chick搜索(1500毫秒)。

How can I handle this scenario? 如何处理这种情况?

  • Once the user triggers a new search via a TextChangedEvent I don't care about the old search, even if its still running. 用户通过TextChangedEvent触发新搜索后,即使它仍在运行,我也不会在意旧搜索。 Is there any way to cancel the old search? 有什么办法可以取消旧的搜索?

Full observable code: 完整的可观察代码:

subscription = WidgetObservable.text(searchText)
                .debounce(300, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                        //do this on main thread because it's a UI element (cannot access a View from a background thread)

                        //get a String representing the new text entered in the EditText
                .map(new Func1<OnTextChangeEvent, String>() {
                    @Override
                    public String call(OnTextChangeEvent onTextChangeEvent) {
                        return onTextChangeEvent.text().toString().trim();
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .doOnNext(new Action1<String>() {
                    @Override
                    public void call(String s) {
                        presenter.handleInput(s);
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(Schedulers.io())
                .filter(new Func1<String, Boolean>() {
                    @Override
                    public Boolean call(String s) {
                        return s != null && s.length() >= 1 && !s.equals("");
                    }
                }).doOnNext(new Action1<String>() {
                    @Override
                    public void call(String s) {
                        Timber.d("searching for string: '%s'", s);
                    }
                })
                        //run SQL query and get a cursor for all the possible search results with the entered search term
                .flatMap(new Func1<String, Observable<SearchBookmarkableAdapterViewModel>>() {
                    @Override
                    public Observable<SearchBookmarkableAdapterViewModel> call(String s) {
                        return presenter.getAdapterViewModelRx(s);
                    }
                })
                .subscribeOn(Schedulers.io())
                        //have the subscriber (the adapter) run on the main thread
                .observeOn(AndroidSchedulers.mainThread())
                        //subscribe the adapter, which receives a stream containing a list of my search result objects and populates the view with them
                .subscribe(new Subscriber<SearchBookmarkableAdapterViewModel>() {
                    @Override
                    public void onCompleted() {
                        Timber.v("Completed loading results");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Timber.e(e, "Error loading results");
                        presenter.onNoResults();
                        //resubscribe so the observable keeps working.
                        subscribeSearchText();
                    }

                    @Override
                    public void onNext(SearchBookmarkableAdapterViewModel searchBookmarkableAdapterViewModel) {
                        Timber.v("Loading data with size: %d into adapter", searchBookmarkableAdapterViewModel.getSize());
                        adapter.loadDataIntoAdapter(searchBookmarkableAdapterViewModel);
                        final int resultCount = searchBookmarkableAdapterViewModel.getSize();
                        if (resultCount == 0)
                            presenter.onNoResults();
                        else
                            presenter.onResults();
                    }
                });

Use switchMap instead of flatMap . 使用switchMap而不是flatMap That will cause it to throw away* the previous query whenever you start a new query. 每当您开始新查询时,这将导致它丢弃*前一个查询。

*How this works: *这是如何工作的:

Whenever the outer source observable produces a new value, switchMap calls your selector to return a new inner observable ( presenter.getAdapterViewModelRx(s) in this case). 每当外部源observable产生一个新值时, switchMap调用您的选择器以返回一个新的内部observable(在这种情况下为presenter.getAdapterViewModelRx(s) )。 switchMap then unsubscribes from the previous inner observable it was listening to and subscribes to the new one. 然后, switchMap 取消订阅它正在侦听的先前内部观察,并订阅新的内部观察。

Unsubscribing from the previous inner observable has two effects: 取消订阅先前的内部可观察对象有两个效果:

  1. Any notification (value, completion, error, etc) produced by the observable will be silently ignored and thrown away. 可观察对象产生的任何通知(值,完成,错误等)将被静默忽略并丢弃。

  2. The observable will be notified that its observer has unsubscribed and can optionally take steps to cancel whatever asynchronous process it represents. 将向可观察对象通知其观察者已取消订阅,并且可以选择采取步骤取消它所代表的任何异步过程。

Whether your abandoned queries are actually cancelled or not is entirely dependent upon the implementation of presenter.getAdapterViewModelRx() . 您放弃的查询是否实际上被取消完全取决于presenter.getAdapterViewModelRx()的实现。 Ideally they would be canceled to avoid needlessly wasting server resources. 理想情况下,将其取消以避免不必要地浪费服务器资源。 But even if they keep running, #1 above prevents your typeahead code from seeing stale results. 但是,即使它们继续运行,上面的#1也可以防止您的预输入代码看到过时的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM