RxCPP(一)编程模型入门- 调度
我们已经在上一节中了解了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)); }