我们已经在上一节中了解了Observables,Operators和Observers。我们已经知道,在Observables和Observers之间,我们可以应用标准的Rx运算符来过滤和转换Streams。在函数式编程的情况下,我们编写不可变函数(没有副作用的函数),不可变性的结果是无序执行的可能性。如果我们可以保证永远不会修改对运算符的输入,那么我们评估的顺序无关紧要。由于Rx程序将操纵多个观察者和订阅者,我们可以将选择执行顺序的任务委派给调度程序模块。默认情况下,RxCpp将在我们称为订阅者方法的线程中安排执行。可以使用observe_on和subscriber_on运算符指定不同的线程。此外,一些Observable运算符将Scheduler作为参数,其中执行可以在Scheduler管理的线程中进行。
该RxCpp库支持以下两种类型的调度:
- ImmediateScheduler
- EventLoopScheduler
ObserveOn
指定一个观察者在哪个调度器上观察这个Observable
Scheduler
"来管理多线程环境中Observable的转场。你可以使用ObserveOn
操作符指定Observable在一个特定的调度器上发送通知给观察者 (调用观察者的onNext
, onCompleted
, onError
方法)。RxCpp库默认是单线程的。 您可以将其配置为使用某些运算符在多个线程中运行:
//----------ObserveOn.cpp #include "rxcpp/rx.hpp" #include <iostream> #include <thread> int main() { //---------------- Generate a range of values //---------------- Apply Square function auto values = rxcpp::observable<>::range(1, 4). map([](int v) { return v * v; }); //------------- Emit the current thread details std::cout << "Main Thread id => " << std::this_thread::get_id() << std::endl; //---------- observe_on another thread.... //---------- make it blocking to values.observe_on(rxcpp::synchronize_new_thread()). as_blocking(). subscribe( [](int v) { std::cout << "Observable Thread id => " << std::this_thread::get_id() << " " << v << std::endl; }, []() { std::cout << "OnCompleted" << std::endl; }); //------------------ Print the main thread details std::cout << "Main Thread id => " << std::this_thread::get_id() << std::endl; }
SubscribeOn
SubscribeOn操作符的作用类似,但它是用于指定Observable本身在特定的调度器上执行,它同样会在那个调度器上给观察者发通知。
ObserveOn操作符的作用类似,但是功能很有限,它指示Observable在一个指定的调度器上给观察者发通知,下面的程序将演示subscribe_on方法的用法:
//---------- SubscribeOn.cpp #include "rxcpp/rx.hpp" #include "rxcpp/rx-test.hpp" #include <iostream> #include <thread> #include <mutex> std::mutex console_mutex; void CTDetails(int val = 0) { console_mutex.lock(); std::cout << "Current Thread id => " << std::this_thread::get_id() << val << std::endl; console_mutex.unlock(); } void Yield(bool y) { if (y) { std::this_thread::yield(); } } int main() { //----------- coordination object auto coordination = rxcpp::serialize_new_thread(); //----------------- retrieve the worker auto worker = coordination.create_coordinator().get_worker(); //-------------- Create an Obsrevable auto values = rxcpp::observable<>::interval(std::chrono::milliseconds(50)). take(5). replay(coordination); // Subscribe from the beginning worker.schedule([&](const rxcpp::schedulers::schedulable&) { values.subscribe( [](long v) {CTDetails(v); }, []() { CTDetails(); }); }); // Wait before subscribing worker.schedule(coordination.now() + std::chrono::milliseconds(125), [&](const rxcpp::schedulers::schedulable&) { values.subscribe( [](long v) {CTDetails(v*v); }, []() { CTDetails(); }); }); // Start emitting worker.schedule([&](const rxcpp::schedulers::schedulable&) { values.connect(); }); // Add blocking subscription to see results values.as_blocking().subscribe(); }
flatmap
FlatMap将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable
FlatMap操作符使用一个指定的函数对原始Observable发射的每一项数据执行变换操作,这个函数返回一个本身也发射数据的Observable,然后FlatMap合并这些Observables发射的数据,最后将合并后的结果当做它自己的数据序列发射。
这个方法是很有用的,例如,当你有一个这样的Observable:它发射一个数据序列,这些数据本身包含Observable成员或者可以变换为Observable,因此你可以创建一个新的Observable发射这些次级Observable发射的数据的完整集合。
注意:
- FlatMap对这些Observables发射的数据做的是合并(merge)操作,因此它们可能是交错的。
- 如果任何一个通过这个flatMap操作产生的单独的Observable调用onError异常终止了,这个Observable自身会立即调用onError并终止。
如下,flatmap将lambda应用于可观察流并生成一个新的可观察流。生成的流合并在一起以提供输出:
#include "rxcpp/rx.hpp" #include <iostream> namespace rxu = rxcpp::util; #include <array> #include <string> //#include <tuple> int main() { std::array< std::string, 4 > a = { {"Praseed", "Peter", "Sanjay","Raju"} }; auto values = rxcpp::observable<>::iterate(a).flat_map( [](std::string v) { std::array<std::string, 3> salutation = { { "Mr." , "Monsieur" , "Sri" } }; return rxcpp::observable<>::iterate(salutation); }, [](std::string f, std::string s) { return s + " " + f; }); values.subscribe([](std::string f) { std::cout << f << std::endl; }, []() {std::cout << "Hello World.." << std::endl; }); }
concatmap
concatmap不会让变换后的Observables发射的数据交错,它按照严格的顺序发射这些数据。
contact和merge
为了让区别更清楚,让我们看一下两个操作符:concat和merge。让我们来看看流的串联是如何工作的。它基本上是一个接一个地添加流的内容,保持顺序:
#include "rxcpp/rx.hpp" #include <iostream> #include <array> int main() { auto o1 = rxcpp::observable<>::range(1, 3); auto o2 = rxcpp::observable<>::range(4, 6); auto values = o1.concat(o2); values. subscribe( [](int v) {printf("OnNext: %d\n", v); }, []() {printf("OnCompleted\n"); }); }
//---------------- Merge.cpp #include "rxcpp/rx.hpp" #include <iostream> #include <array> int main() { auto o1 = rxcpp::observable<>::range(1, 3); auto o2 = rxcpp::observable<>::from(4, 5, 6); auto values = o1.merge(o2); values.subscribe( [](int v) {printf("OnNext: %d\n", v); }, []() {printf("OnCompleted\n"); }); }
更多重要的操作符
现在我们了解了反应性编程模型的关键,因为我们讨论了一些基本主题,比如可观察性、观察者、操作符和调度程序。为了更好地编写逻辑,我们应该了解更多的运算符。
tap
是一个RxCPP管道操作符,返回与源可观察相同的可观察值,可用于执行副作用,例如记录源可观察值发出的每个值。我们将探索tap操作符,它有助于查看流的内容:
//----------- TapExample.cpp #include "rxcpp/rx.hpp" #include <iostream> int main() { //---- Create a mapped Observable auto ints = rxcpp::observable<>::range(1, 3). map([](int n) {return n * n; }); //---- Apply the tap operator...The Operator //---- will act as a filter/debug operator auto values = ints. tap( [](int v) {printf("Tap - OnNext: %d\n", v); }, []() {printf("Tap - OnCompleted\n"); }); //------- Do some action values. subscribe( [](int v) {printf("Subscribe - OnNext: %d\n", v); }, []() {printf("Subscribe - OnCompleted\n"); }); }
defer
直到有观察者订阅时才创建Observable,并且为每个观察者创建一个新的Observable
defer操作符接受一个你选择的Observable工厂函数作为单个参数。这个函数没有参数,返回一个Observable。
defer操作符会一直等待直到有观察者订阅它,然后它使用Observable工厂方法生成一个Observable。它对每个观察者都这样做,因此尽管每个订阅者都以为自己订阅的是同一个Observable,事实上每个订阅者获取的是它们自己的单独的数据序列。
在某些情况下,等待直到最后一分钟(就是知道订阅发生时)才生成Observable可以确保Observable包含最新的数据
当有人试图连接到指定的可观察对象时,我们调用observable_factory lambda:
//----------- DeferExample.cpp #include "rxcpp/rx.hpp" #include <iostream> int main() { auto observable_factory = []() { return rxcpp::observable<>::range(1, 3). map([](int n) {return n * n; }); }; auto ints = rxcpp::observable<>::defer(observable_factory); ints. subscribe( [](int v) {printf("OnNext: %d\n", v); }, []() {printf("OnCompleted\n"); }); ints. subscribe( [](int v) {printf("2nd OnNext: %d\n", v); }, []() {printf("2nd OnCompleted\n"); }); }
buffer
buffer操作符定期收集Observable的数据放进一个数据包裹,然后发射这些数据包裹,而不是一次发射一个值。
注意:如果原来的Observable发射了一个onError通知,Buffer会立即传递这个通知,而不是首先发射缓存的数据,即使在这之前缓存中包含了原始Observable发射的数据。
Window操作符与Buffer类似,但是它在发射之前把收集到的数据放进单独的Observable,而不是放进一个数据结构。详见:https://mcxiaoke.gitbooks.io/rxdocs/content/operators/Buffer.html
buffer操作符发出一个包含一个可观察对象的非重叠内容的可观察对象,每个可观察对象最多包含count参数指定的项数。这将帮助我们以适合内容的方式处理项目:
//----------- BufferExample.cpp #include "rxcpp/rx.hpp" #include <iostream> int main() { auto values = rxcpp::observable<>::range(1, 10).buffer(3); values. subscribe( [](std::vector<int> v) { printf("OnNext:{"); std::for_each(v.begin(), v.end(), [](int a) { printf(" %d", a); }); printf("}\n"); }, []() {printf("OnCompleted\n"); }); }
timer
创建一个Observable,它在一个给定的延迟后发射一个特殊的值。
这个函数在库中有不同的版本,timer操作符默认在computation调度器上执行。有一个变体可以通过可选参数指定Scheduler:
//----------- TimerExample.cpp #include "rxcpp/rx.hpp" #include "rxcpp/rx-test.hpp" #include <iostream> #include <thread> #include <chrono> int main() { auto scheduler = rxcpp::observe_on_new_thread(); auto period = std::chrono::seconds(3); auto values = rxcpp::observable<>::timer(period, scheduler). finally([]() { std::cout << "The final action, thread id: " << std::this_thread::get_id() << std::endl; }); values. as_blocking(). subscribe( [](int v) { std::cout << "OnNext: " << v << "thread id: " << std::this_thread::get_id() << std::endl; }, []() {std::cout << "OnCompleted, thread id: " << std::this_thread::get_id() << std::endl; }); std::cout << "main thread id: " << std::this_thread::get_id() << std::endl; //必须在主线程sleep,否则finally中的内容打印不全,因为程序退出后,线程销毁 std::this_thread::sleep_for(std::chrono::milliseconds(100)); }