Rx 范例
Start 方法- 异步运行代码异步后台操作
public static void StartBackgroundWork() {
Console.WriteLine("Shows use of Start to start on a background thread:");
var o = Observable.Start(() =>
{
//This starts on a background thread.
Console.WriteLine("From background thread. Does not block main thread.");
Console.WriteLine("Calculating...");
Thread.Sleep(3000);
Console.WriteLine("Background work completed.");
}).Finally(() => Console.WriteLine("Main thread completed."));
Console.WriteLine("\r\n\t In Main Thread...\r\n");
o.Wait(); // Wait for completion of background operation.
}
异步执行耗时同步函数
一个长耗时的异步方法,并不会马上执行,而是等到被订阅的时候。Observable对象每次被创建、订阅都会执行这个方法,可以多次执行。
// Synchronous operation
public DataType DoLongRunningOperation(string param)
{
...
}
public IObservable<DataType> LongRunningOperationAsync(string param)
{
return Observable.Create<DataType>(
o => Observable.ToAsync<string,DataType>(DoLongRunningOperation)(param).Subscribe(o)
);
}
CombineLatest方法 - 并行执行
将多个Observable对象合并为一个Observable对象中,当全部子Observable对象都生成结果后,主Observable返回一个结果列表。(Merges the specified observable sequences into one observable sequence by emitting a list with the latest source elements whenever any of the observable sequences produces an element.)
public async void ParallelExecutionTest()
{
var o = Observable.CombineLatest(
Observable.Start(() => { Console.WriteLine("Executing 1st on Thread: {0}", Thread.CurrentThread.ManagedThreadId); return "Result A"; }),
Observable.Start(() => { Console.WriteLine("Executing 2nd on Thread: {0}", Thread.CurrentThread.ManagedThreadId); return "Result B"; }),
Observable.Start(() => { Console.WriteLine("Executing 3rd on Thread: {0}", Thread.CurrentThread.ManagedThreadId); return "Result C"; })
).Finally(() => Console.WriteLine("Done!"));
foreach (string r in await o.FirstAsync())
Console.WriteLine(r);
}
Result
Executing 1st on Thread: 3
Executing 2nd on Thread: 4
Executing 3rd on Thread: 3
Done!
Result A
Result B
Result C
注意 不在支持ForkJoin方法,改为CombineLatest函数
创建Disposable & Scheduler - 可取消异步操作
本例在后台生成一系列整数,直到主线程取消。启动后台操作时生成了一个Scheduler对象和CancellationTokenSource对象.
取消操作更多信息请见 System.Threading.CancellationTokenSource .
IObservable<int> ob =
Observable.Create<int>(o =>
{
var cancel = new CancellationDisposable(); // internally creates a new CancellationTokenSource
NewThreadScheduler.Default.Schedule(() =>
{
int i = 0;
for (; ; )
{
Thread.Sleep(200); // here we do the long lasting background operation
if (!cancel.Token.IsCancellationRequested) // check cancel token periodically
o.OnNext(i++);
else
{
Console.WriteLine("Aborting because cancel event was signaled!");
o.OnCompleted(); // will not make it to the subscriber
return;
}
}
}
);
return cancel;
}
);
IDisposable subscription = ob.Subscribe(i => Console.WriteLine(i));
Console.WriteLine("Press any key to cancel");
Console.ReadKey();
subscription.Dispose();
Console.WriteLine("Press any key to quit");
Console.ReadKey(); // give background thread chance to write the cancel acknowledge message
可观察者操作
观察事件(Observing an Event)
class ObserveEvent_Simple
{
public static event EventHandler SimpleEvent;
static void Main()
{
// To consume SimpleEvent as an IObservable:
var eventAsObservable = Observable.FromEventPattern(
ev => SimpleEvent += ev,
ev => SimpleEvent -= ev);
}
}
另外,可以使用EventArgs参数:
public static event EventHandler<EventArgs> SimpleEvent;
private static void Main(string[] args) {
var eventAsObservable = Observable.FromEventPattern<EventArgs>
(ev => SimpleEvent += ev,
ev => SimpleEvent -= ev);
}
观察事件范例的扩展(Observing an Event)(expanded)
class ObserveEvent_Simple
{
public static event EventHandler SimpleEvent;
private static void Main()
{
Console.WriteLine("Setup observable");
// To consume SimpleEvent as an IObservable:
var eventAsObservable = Observable.FromEventPattern(
ev => SimpleEvent += ev,
ev => SimpleEvent -= ev);
// SimpleEvent is null until we subscribe
Console.WriteLine(SimpleEvent == null ? "SimpleEvent == null" : "SimpleEvent != null");
Console.WriteLine("Subscribe");
//Create two event subscribers
var s = eventAsObservable.Subscribe(args => Console.WriteLine("Received event for s subscriber"));
var t = eventAsObservable.Subscribe(args => Console.WriteLine("Received event for t subscriber"));
// After subscribing the event handler has been added
Console.WriteLine(SimpleEvent == null ? "SimpleEvent == null" : "SimpleEvent != null");
Console.WriteLine("Raise event");
if (null != SimpleEvent)
{
SimpleEvent(null, EventArgs.Empty);
}
// Allow some time before unsubscribing or event may not happen
Thread.Sleep(100);
Console.WriteLine("Unsubscribe");
s.Dispose();
t.Dispose();
// After unsubscribing the event handler has been removed
Console.WriteLine(SimpleEvent == null ? "SimpleEvent == null" : "SimpleEvent != null");
Console.ReadKey();
}
}
Silverlight中观察鼠标移动事件
var mouseMove = Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove");
mouseMove.ObserveOnDispatcher()
.Subscribe(args => Debug.WriteLine(args.EventArgs.GetPosition(this)));
ObserveOnDispatcher需要System.Reactive.Windows.Threading引用,可在Nuget的Reactive Extensions - Silverlight Helpers找到.
观察事件-泛型(Observing an Event - Generic)
class ObserveEvent_Generic
{
public class SomeEventArgs : EventArgs { }
public static event EventHandler<SomeEventArgs> GenericEvent;
static void Main()
{
// To consume GenericEvent as an IObservable:
IObservable<EventPattern<SomeEventArgs>> eventAsObservable = Observable.FromEventPattern<SomeEventArgs>(
ev => GenericEvent += ev,
ev => GenericEvent -= ev );
}
}
观察事件-非泛型(Observing an Event - Non-Generic)
class ObserveEvent_NonGeneric
{
public class SomeEventArgs : EventArgs { }
public delegate void SomeNonGenericEventHandler(object sender, SomeEventArgs e);
public static event SomeNonGenericEventHandler NonGenericEvent;
static void Main()
{
// To consume NonGenericEvent as an IObservable, first inspect the type of EventArgs used in the second parameter of the delegate.
// In this case, it is SomeEventArgs. Then, use as shown below.
IObservable<IEvent<SomeEventArgs>> eventAsObservable = Observable.FromEvent(
(EventHandler<SomeEventArgs> ev) => new SomeNonGenericEventHandler(ev),
ev => NonGenericEvent += ev,
ev => NonGenericEvent -= ev);
}
}
观察异步操作
class Observe_IAsync
{
static void Main()
{
// We will use Stream's BeginRead and EndRead for this sample.
Stream inputStream = Console.OpenStandardInput();
// To convert an asynchronous operation that uses the IAsyncResult pattern to a function that returns an IObservable, use the following format.
// For the generic arguments, specify the types of the arguments of the Begin* method, up to the AsyncCallback.
// If the End* method returns a value, append this as your final generic argument.
var read = Observable.FromAsyncPattern<byte[], int, int, int>(inputStream.BeginRead, inputStream.EndRead);
// Now, you can get an IObservable instead of an IAsyncResult when calling it.
byte[] someBytes = new byte[10];
IObservable<int> observable = read(someBytes, 0, 10);
}
}
上面代码提供了一个观察者,但在多数场景下是不够的,等多信息见
Creating an observable sequence and
c# - What is the proper way to create an Observable which reads a stream to the end - Stack Overflow.
观察一个可枚举对象(Observing a Generic IEnumerable)
class Observe_GenericIEnumerable
{
static void Main()
{
IEnumerable<int> someInts = new List<int> { 1, 2, 3, 4, 5 };
// To convert a generic IEnumerable into an IObservable, use the ToObservable extension method.
IObservable<int> observable = someInts.ToObservable();
}
}
观察非泛型可枚举对象Observing a Non-Generic IEnumerable - Single Type
class Observe_NonGenericIEnumerableSingleType
{
static void Main()
{
IEnumerable someInts = new object[] { 1, 2, 3, 4, 5 };
// To convert a non-generic IEnumerable that contains elements of a single type,
// first use Cast<> to change the non-generic enumerable into a generic enumerable,
// then use ToObservable.
IObservable<int> observable = someInts.Cast<int>().ToObservable();
}
}
Observing a Non-Generic IEnumerable - Multiple Types
定时触发的观察对象(Observing the Passing of Time)
class Observe_Time
{
static void Main()
{
// To observe time passing, use the Observable.Interval function.
// It will notify you on a time interval you specify.
// 0 after 1s, 1 after 2s, 2 after 3s, etc.
IObservable<long> oneNumberPerSecond = Observable.Interval(TimeSpan.FromSeconds(1));
IObservable<long> alsoOneNumberPerSecond = Observable.Interval(1000 /* milliseconds */);
}
}
限制操作(Restriction Operators)
Where - 范例
class Where_Simple
{
static void Main()
{
var oneNumberPerSecond = Observable.Interval(TimeSpan.FromSeconds(1));
var lowNums = from n in oneNumberPerSecond
where n < 5
select n;
Console.WriteLine("Numbers < 5:");
lowNums.Subscribe(lowNum =>
{
Console.WriteLine(lowNum);
});
Console.ReadKey();
}
}
结果
Numbers < 5:
0 (after 1s)
1 (after 2s)
2 (after 3s)
3 (after 4s)
4 (after 5s)
Where - 过滤条件
class Where_DrillDown
{
class Customer
{
public Customer() { Orders = new ObservableCollection<Order>(); }
public string CustomerName { get; set; }
public string Region { get; set; }
public ObservableCollection<Order> Orders { get; private set; }
}
class Order
{
public int OrderId { get; set; }
public DateTimeOffset OrderDate { get; set; }
}
static void Main()
{
var customers = new ObservableCollection<Customer>();
var customerChanges = Observable.FromEventPattern(
(EventHandler<NotifyCollectionChangedEventArgs> ev)
=> new NotifyCollectionChangedEventHandler(ev),
ev => customers.CollectionChanged += ev,
ev => customers.CollectionChanged -= ev);
//关注添加记录,而且记录的Region==WA
var watchForNewCustomersFromWashington =
from c in customerChanges
where c.EventArgs.Action == NotifyCollectionChangedAction.Add
from cus in c.EventArgs.NewItems.Cast<Customer>().ToObservable()
where cus.Region == "WA"
select cus;
Console.WriteLine("New customers from Washington and their orders:");
watchForNewCustomersFromWashington.Subscribe(cus =>
{
Console.WriteLine("Customer {0}:", cus.CustomerName);
foreach (var order in cus.Orders)
{
Console.WriteLine("Order {0}: {1}", order.OrderId, order.OrderDate);
}
});
customers.Add(new Customer
{
CustomerName = "Lazy K Kountry Store",
Region = "WA",
Orders = { new Order { OrderDate = DateTimeOffset.Now, OrderId = 1 } }
});
Thread.Sleep(1000);
customers.Add(new Customer
{
CustomerName = "Joe's Food Shop",
Region = "NY",
Orders = { new Order { OrderDate = DateTimeOffset.Now, OrderId = 2 } }
});
Thread.Sleep(1000);
customers.Add(new Customer
{
CustomerName = "Trail's Head Gourmet Provisioners",
Region = "WA",
Orders = { new Order { OrderDate = DateTimeOffset.Now, OrderId = 3 } }
});
Console.ReadKey();
}
}
结果
New customers from Washington and their orders:
Customer Lazy K Kountry Store: (after 0s)
Order 1: 11/20/2009 11:52:02 AM -06:00
Customer Trail's Head Gourmet Provisioners: (after 2s)
Order 3: 11/20/2009 11:52:04 AM -06:00
LinQ映射操作(Projection Operators)
Select - 范例
class Select_Simple
{
static void Main()
{
var oneNumberPerSecond = Observable.Interval(TimeSpan.FromSeconds(1));
var numbersTimesTwo = from n in oneNumberPerSecond
select n * 2;
Console.WriteLine("Numbers * 2:");
numbersTimesTwo.Subscribe(num =>
{
Console.WriteLine(num);
});
Console.ReadKey();
}
}
结果
Numbers * 2:
0 (after 1s)
2 (after 2s)
4 (after 3s)
6 (after 4s)
8 (after 5s)
LinQ Select - 变换
class Select_Transform
{
static void Main()
{
var oneNumberPerSecond = Observable.Interval(TimeSpan.FromSeconds(1));
var stringsFromNumbers = from n in oneNumberPerSecond
select new string('*', (int)n);
Console.WriteLine("Strings from numbers:");
stringsFromNumbers.Subscribe(num =>
{
Console.WriteLine(num);
});
Console.ReadKey();
}
}
结果
Strings from numbers:
(after 0s)
* (after 1s)
** (after 2s)
*** (after 3s)
**** (after 4s)
***** (after 5s)
****** (after 6s)
LinQ Select - 索引(Indexed)
class Where_Indexed
{
class TimeIndex
{
public TimeIndex(int index, DateTimeOffset time)
{
Index = index;
Time = time;
}
public int Index { get; set; }
public DateTimeOffset Time { get; set; }
}
static void Main()
{
var clock = Observable.Interval(TimeSpan.FromSeconds(1))
.Select((t, index) => new TimeIndex(index, DateTimeOffset.Now));
clock.Subscribe(timeIndex =>
{
Console.WriteLine(
"Ding dong. The time is now {0:T}. This is event number {1}.",
timeIndex.Time,
timeIndex.Index);
});
Console.ReadKey();
}
}
结果
Ding dong. The time is now 1:55:00 PM. This is event number 0. (after 0s)
Ding dong. The time is now 1:55:01 PM. This is event number 1. (after 1s)
Ding dong. The time is now 1:55:02 PM. This is event number 2. (after 2s)
Ding dong. The time is now 1:55:03 PM. This is event number 3. (after 3s)
Ding dong. The time is now 1:55:04 PM. This is event number 4. (after 4s)
Ding dong. The time is now 1:55:05 PM. This is event number 5. (after 5s)
分组
LinQ Group By - 范例
本例统计按键次数. :)
class GroupBy_Simple
{
static IEnumerable<ConsoleKeyInfo> KeyPresses()
{
for (; ; )
{
var currentKey = Console.ReadKey(true);
if (currentKey.Key == ConsoleKey.Enter)
yield break;
else
yield return currentKey;
}
}
static void Main()
{
var timeToStop = new ManualResetEvent(false);
var keyPresses = KeyPresses().ToObservable();
var groupedKeyPresses =
from k in keyPresses
group k by k.Key into keyPressGroup
select keyPressGroup;
Console.WriteLine("Press Enter to stop. Now bang that keyboard!");
groupedKeyPresses.Subscribe(keyPressGroup =>
{
int numberPresses = 0;
keyPressGroup.Subscribe(keyPress =>
{
Console.WriteLine(
"You pressed the {0} key {1} time(s)!",
keyPress.Key,
++numberPresses);
},
() => timeToStop.Set());
});
timeToStop.WaitOne();
}
}
结果
依赖于具体按键情况,输出大概如下:
Press Enter to stop. Now bang that keyboard!
You pressed the A key 1 time(s)!
You pressed the A key 2 time(s)!
You pressed the B key 1 time(s)!
You pressed the B key 2 time(s)!
You pressed the C key 1 time(s)!
You pressed the C key 2 time(s)!
You pressed the C key 3 time(s)!
You pressed the A key 3 time(s)!
You pressed the B key 3 time(s)!
You pressed the A key 4 time(s)!
You pressed the A key 5 time(s)!
You pressed the C key 4 time(s)!
时间相关操作(Time-Related Operators)
Buffer - 范例
Buffer名字很奇特,但概念很简单.
假设有一个邮件程序每5分钟检查一次新邮件。但可能随时都会收到邮件,只能每5分钟批量获取一次邮件。我们使用缓冲来模拟这个情况。每5分钟触发一次订阅,获取期间收到的邮件。
class Buffer_Simple
{
static IEnumerable<string> EndlessBarrageOfEmail()
{
var random = new Random();
var emails = new List<String> { "Here is an email!", "Another email!", "Yet another email!" };
for (; ; )
{
// 随机时间间隔内返回随机邮件
yield return emails[random.Next(emails.Count)];
Thread.Sleep(random.Next(1000));
}
}
static void Main()
{
var myInbox = EndlessBarrageOfEmail().ToObservable();
// 为了方便观察将5分钟改为3秒检查一次新邮件:)
var getMailEveryThreeSeconds = myInbox.Buffer(TimeSpan.FromSeconds(3)); // Was .BufferWithTime(...
getMailEveryThreeSeconds.Subscribe(emails =>
{
Console.WriteLine("You've got {0} new messages! Here they are!", emails.Count());
foreach (var email in emails)
{
Console.WriteLine("> {0}", email);
}
Console.WriteLine();
});
Console.ReadKey();
}
}
Result
You've got 5 new messages! Here they are! (after 3s)
> Here is an email!
> Another email!
> Here is an email!
> Another email!
> Here is an email!
You've got 6 new messages! Here they are! (after 6s)
> Another email!
> Another email!
> Here is an email!
> Here is an email!
> Another email!
> Another email!
Delay - 范例
class Delay_Simple
{
static void Main()
{
var oneNumberEveryFiveSeconds = Observable.Interval(TimeSpan.FromSeconds(5));
// Instant echo
oneNumberEveryFiveSeconds.Subscribe(num =>
{
Console.WriteLine(num);
});
// One second delay
oneNumberEveryFiveSeconds.Delay(TimeSpan.FromSeconds(1)).Subscribe(num =>
{
Console.WriteLine("...{0}...", num);
});
// Two second delay
oneNumberEveryFiveSeconds.Delay(TimeSpan.FromSeconds(2)).Subscribe(num =>
{
Console.WriteLine("......{0}......", num);
});
Console.ReadKey();
}
}
Result
0 (after 5s)
…0… (after 6s)
……0…… (after 7s)
1 (after 10s)
…1… (after 11s)
……1…… (after 12s)
Interval - 每间隔指定时间触发一次
internal class Interval_Simple
{
private static void Main()
{
IObservable<long> observable = Observable.Interval(TimeSpan.FromSeconds(1));
using (observable.Subscribe(Console.WriteLine))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
0 (after 1s)
1 (after 2s)
2 (after 3s)
3 (after 4s)
…
采样(Sample) - Simple
internal class Sample_Simple
{
private static void Main()
{
// Generate sequence of numbers, (an interval of 50 ms seems to result in approx 16 per second).
IObservable<long> observable = Observable.Interval(TimeSpan.FromMilliseconds(50));
// Sample the sequence every second
using (observable.Sample(TimeSpan.FromSeconds(1)).Timestamp().Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
15: 24/11/2009 15:40:45 (after 1s)
31: 24/11/2009 15:40:46 (after 2s)
47: 24/11/2009 15:40:47 (after 3s)
64: 24/11/2009 15:40:48 (after 4s)
…
Throttle(截断) - Simple
截断:事件在指定时间段内频繁触发,则进行屏蔽。例如,将一个textbox的textChange事件截断0.5秒,用户输入时0.5秒内不再触发事件,且0.5秒后事件才会触发。对于一个查找筐, 我们不希望每次输入都启动搜索过程,而是等到用户停止输入才进行。
SearchTextChangedObservable = Observable.FromEventPattern<TextChangedEventArgs>(this.textBox, "TextChanged");
_currentSubscription = SearchTextChangedObservable.Throttle(TimeSpan.FromSeconds(.5)).ObserveOnDispatcher().Subscribe(e => this.ListItems.Add(this.textBox.Text));
另一个例子:
internal class Throttle_Simple
{
// Generates events with interval that alternates between 500ms and 1000ms every 5 events
static IEnumerable<int> GenerateAlternatingFastAndSlowEvents()
{
int i = 0;
while(true)
{
if(i > 1000)
{
yield break;
}
yield return i;
Thread.Sleep( i++ % 10 < 5 ? 500 : 1000);
}
}
private static void Main()
{
var observable = GenerateAlternatingFastAndSlowEvents().ToObservable().Timestamp();
var throttled = observable.Throttle(TimeSpan.FromMilliseconds(750));
using (throttled.Subscribe(x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
结果
5: <timestamp>
6: <timestamp>
7: <timestamp>
8: <timestamp>
9: <timestamp>
15: <timestamp>
16: <timestamp>
17: <timestamp>
18: <timestamp>
19: <timestamp>
…etc
Interval - 使用TimeInterval()添加时间戳 - Simple
internal class TimeInterval_Simple
{
// Like TimeStamp but gives the time-interval between successive values
private static void Main()
{
var observable = Observable.Interval(TimeSpan.FromMilliseconds(750)).TimeInterval();
using (observable.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Interval)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
0: 00:00:00.8090459 (1st value)
1: 00:00:00.7610435 (2nd value)
2: 00:00:00.7650438 (3rd value)
…
Interval - 使用TimeInterval() - 再移除时间戳
internal class TimeInterval_Remove
{
private static void Main()
{
// Add a time interval
var observable = Observable.Interval(TimeSpan.FromMilliseconds(750)).TimeInterval();
// Remove it again
using (observable.RemoveTimeInterval().Subscribe(Console.WriteLine))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
0
1
2
…
超时(Timeout) - Simple
internal class Timeout_Simple
{
private static void Main()
{
Console.WriteLine(DateTime.Now);
// create a single event in 10 seconds time
var observable = Observable.Timer(TimeSpan.FromSeconds(10)).Timestamp();
// raise exception if no event received within 9 seconds
var observableWithTimeout = Observable.Timeout(observable, TimeSpan.FromSeconds(9));
using (observableWithTimeout.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp),
ex => Console.WriteLine("{0} {1}", ex.Message, DateTime.Now)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
02/12/2009 10:13:00
Press any key to unsubscribe
The operation has timed out. 02/12/2009 10:13:09
…
定时器(Timer) - Simple
Observable.Interval是Observable.Timer的简单封装.
internal class Timer_Simple
{
private static void Main()
{
Console.WriteLine(DateTime.Now);
//5秒后触发,每秒触发一次
var observable = Observable.Timer(TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(1)).Timestamp();
// or, equivalently
// var observable = Observable.Timer(DateTime.Now + TimeSpan.FromSeconds(5),
// TimeSpan.FromSeconds(1)).Timestamp();
using (observable.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
02/12/2009 10:02:29
Press any key to unsubscribe
0: 02/12/2009 10:02:34(after 5s)
1: 02/12/2009 10:02:35 (after 6s)
2: 02/12/2009 10:02:36 (after 7s)
…
时间戳(Timestamp) - Simple
触发时传递一个系统本地格式的时间戳(Adds a TimeStamp to each element using the system's local time.)
internal class Timestamp_Simple
{
private static void Main()
{
var observable = Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp();
using (observable.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
0: 24/11/2009 15:40:45 (after 1s)
1: 24/11/2009 15:40:46 (after 2s)
2: 24/11/2009 15:40:47 (after 3s)
3: 24/11/2009 15:40:48 (after 4s)
…
移除时间戳(Timestamp - Remove)
internal class Timestamp_Remove
{
private static void Main()
{
// Add timestamp
var observable = Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp();
// Remove it
using (observable.RemoveTimestamp().Subscribe(Console.WriteLine))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
0 (after 1s)
1 (after 2s)
2 (after 3s)
3 (after 4s)
…
Window and Joins
Window
将流分为多个窗口时间。如,五秒窗口包含这五秒内所有推入的元素。
IObservable<long> mainSequence = Observable.Interval(TimeSpan.FromSeconds(1));
IObservable<IObservable<long>> seqWindowed = mainSequence.Window(() =>
{
IObservable<long> seqWindowControl = Observable.Interval(TimeSpan.FromSeconds(6));
return seqWindowControl;
});
seqWindowed.Subscribe(seqWindow =>
{
Console.WriteLine("\nA new window into the main sequence has opened: {0}\n",
DateTime.Now.ToString());
seqWindow.Subscribe(x => { Console.WriteLine("Integer : {0}", x); });
});
Console.ReadLine();
GroupJoin - 将两个流按其元素的某个属性进行关联(Joins two streams matching by one of their attributes)
var leftList = new List<string[]>();
leftList.Add(new string[] { "2013-01-01 02:00:00", "Batch1" });
leftList.Add(new string[] { "2013-01-01 03:00:00", "Batch2" });
leftList.Add(new string[] { "2013-01-01 04:00:00", "Batch3" });
var rightList = new List<string[]>();
rightList.Add(new string[] { "2013-01-01 01:00:00", "Production=2" });
rightList.Add(new string[] { "2013-01-01 02:00:00", "Production=0" });
rightList.Add(new string[] { "2013-01-01 03:00:00", "Production=3" });
var l = leftList.ToObservable();
var r = rightList.ToObservable();
var q = l.GroupJoin(r,
_ => Observable.Never<Unit>(), // windows from each left event going on forever
_ => Observable.Never<Unit>(), // windows from each right event going on forever
(left, obsOfRight) => Tuple.Create(left, obsOfRight)); // create tuple of left event with observable of right events
// e is a tuple with two items, left and obsOfRight
q.Subscribe(e =>
{
var xs = e.Item2;
xs.Where(
x => x[0] == e.Item1[0]) // filter only when datetime matches
.Subscribe(
v =>
{
Console.WriteLine(
string.Format("{0},{1} and {2},{3} occur at the same time",
e.Item1[0],
e.Item1[1],
v[0],
v[1]
));
});
});
Range
生成一系列数值,多用于测试。
Range - 输出1到10.
IObservable<int> source = Observable.Range(1, 10);
IDisposable subscription = source.Subscribe(
x => Console.WriteLine("OnNext: {0}", x),
ex => Console.WriteLine("OnError: {0}", ex.Message),
() => Console.WriteLine("OnCompleted"));
Console.WriteLine("Press ENTER to unsubscribe...");
Console.ReadLine();
subscription.Dispose();
生成器(Generate)
有多个重载的Generate.
Generate - simple
可创建定时执行并可退出执行。(A simple use is to replicate Interval but have the sequence stop.)
internal class Generate_Simple
{
private static void Main()
{
var observable =
Observable.Generate(1, x => x < 6, x => x + 1, x => x,
x=>TimeSpan.FromSeconds(1)).Timestamp();
using (observable.Subscribe(x => Console.WriteLine("{0}, {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
Result
1: 24/11/2009 15:40:45 (after 1s)
2: 24/11/2009 15:40:46 (after 2s)
3: 24/11/2009 15:40:47 (after 3s)
4: 24/11/2009 15:40:48 (after 4s)
5: 24/11/2009 15:40:49 (after 5s)
ISubject<T> and ISubject<T1, T2>
接口ISubject有多个实现.
Ping Pong Actor Model with ISubject<T1, T2>
using System;
using System.Collections.Generic;
using System.Linq;
namespace RxPingPong
{
/// <summary>Simple Ping Pong Actor model using Rx </summary>
/// <remarks>
/// You'll need to install the Reactive Extensions (Rx) for this to work.
/// You can get the installer from <see href="http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx"/>
/// </remarks>
class Program
{
static void Main(string[] args)
{
var ping = new Ping();
var pong = new Pong();
Console.WriteLine("Press any key to stop ...");
var pongSubscription = ping.Subscribe(pong);
var pingSubscription = pong.Subscribe(ping);
Console.ReadKey();
pongSubscription.Dispose();
pingSubscription.Dispose();
Console.WriteLine("Ping Pong has completed.");
}
}
class Ping : ISubject<Pong, Ping>
{
#region Implementation of IObserver<Pong>
/// <summary>
/// Notifies the observer of a new value in the sequence.
/// </summary>
public void OnNext(Pong value)
{
Console.WriteLine("Ping received Pong.");
}
/// <summary>
/// Notifies the observer that an exception has occurred.
/// </summary>
public void OnError(Exception exception)
{
Console.WriteLine("Ping experienced an exception and had to quit playing.");
}
/// <summary>
/// Notifies the observer of the end of the sequence.
/// </summary>
public void OnCompleted()
{
Console.WriteLine("Ping finished.");
}
#endregion
#region Implementation of IObservable<Ping>
/// <summary>
/// Subscribes an observer to the observable sequence.
/// </summary>
public IDisposable Subscribe(IObserver<Ping> observer)
{
return Observable.Interval(TimeSpan.FromSeconds(2))
.Where(n => n < 10)
.Select(n => this)
.Subscribe(observer);
}
#endregion
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
OnCompleted();
}
#endregion
}
class Pong : ISubject<Ping, Pong>
{
#region Implementation of IObserver<Ping>
/// <summary>
/// Notifies the observer of a new value in the sequence.
/// </summary>
public void OnNext(Ping value)
{
Console.WriteLine("Pong received Ping.");
}
/// <summary>
/// Notifies the observer that an exception has occurred.
/// </summary>
public void OnError(Exception exception)
{
Console.WriteLine("Pong experienced an exception and had to quit playing.");
}
/// <summary>
/// Notifies the observer of the end of the sequence.
/// </summary>
public void OnCompleted()
{
Console.WriteLine("Pong finished.");
}
#endregion
#region Implementation of IObservable<Pong>
/// <summary>
/// Subscribes an observer to the observable sequence.
/// </summary>
public IDisposable Subscribe(IObserver<Pong> observer)
{
return Observable.Interval(TimeSpan.FromSeconds(1.5))
.Where(n => n < 10)
.Select(n => this)
.Subscribe(observer);
}
#endregion
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
OnCompleted();
}
#endregion
}
}
Result
1: Ping received Pong.
2: Pong received Ping.
3: Ping received Pong.
4: Pong received Ping.
5: Ping received Pong.
组合操作
Merge
Merge 操作组合多个系列。例如将两个流合并为一个,便于使用一个订阅。注意使用using包住Observable对象,确保释放。
class Merge
{
private static IObservable<int> Xs
{
get { return Generate(0, new List<int> {1, 2, 2, 2, 2}); }
}
private static IObservable<int> Ys
{
get { return Generate(100, new List<int> {2, 2, 2, 2, 2}); }
}
private static IObservable<int> Generate(int initialValue, IList<int> intervals)
{
// work-around for Observable.Generate calling timeInterval before resultSelector
intervals.Add(0);
return Observable.Generate(initialValue,
x => x < initialValue + intervals.Count - 1,
x => x + 1,
x => x,
x => TimeSpan.FromSeconds(intervals[x - initialValue]));
}
private static void Main()
{
Console.WriteLine("Press any key to unsubscribe");
using (Xs.Merge(Ys).Timestamp().Subscribe(
z => Console.WriteLine("{0,3}: {1}", z.Value, z.Timestamp),
() => Console.WriteLine("Completed, press a key")))
{
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
0: 11/12/2009 12:17:44
100: 11/12/2009 12:17:45
1: 11/12/2009 12:17:46
101: 11/12/2009 12:17:47
2: 11/12/2009 12:17:48
102: 11/12/2009 12:17:49
3: 11/12/2009 12:17:50
103: 11/12/2009 12:17:51
4: 11/12/2009 12:17:52
104: 11/12/2009 12:17:53
Publish - 在多个观察对象间共享订阅(Sharing a subscription with multiple Observers)
class Publish
{
private static void Main()
{
var unshared = Observable.Range(1, 4);
// Each subscription starts a new sequence
unshared.Subscribe(i => Console.WriteLine("Unshared Subscription #1: " + i));
unshared.Subscribe(i => Console.WriteLine("Unshared Subscription #2: " + i));
Console.WriteLine();
// By using publish the subscriptions are shared, but the sequence doesn't start until Connect() is called.
var shared = unshared.Publish();
shared.Subscribe(i => Console.WriteLine("Shared Subscription #1: " + i));
shared.Subscribe(i => Console.WriteLine("Shared Subscription #2: " + i));
shared.Connect();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
Unshared Subscription #1: 1
Unshared Subscription #1: 2
Unshared Subscription #1: 3
Unshared Subscription #1: 4
Unshared Subscription #2: 1
Unshared Subscription #2: 2
Unshared Subscription #2: 3
Unshared Subscription #2: 4
Shared Subscription #1: 1
Shared Subscription #2: 1
Shared Subscription #1: 2
Shared Subscription #2: 2
Shared Subscription #1: 3
Shared Subscription #2: 3
Shared Subscription #1: 4
Shared Subscription #2: 4
Zip
class Zip
{
// same code as above for Merge...
private static void Main()
{
Console.WriteLine("Press any key to unsubscribe");
using (Xs.Zip(Ys, (x, y) => x + y).Timestamp().Subscribe(
z => Console.WriteLine("{0,3}: {1}", z.Value, z.Timestamp),
() => Console.WriteLine("Completed, press a key")))
{
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
100: 11/12/2009 12:17:45
102: 11/12/2009 12:17:47
104: 11/12/2009 12:17:49
106: 11/12/2009 12:17:51
108: 11/12/2009 12:17:53
CombineLatest
class CombineLatest
{
// same code as above for Merge...
private static void Main()
{
Console.WriteLine("Press any key to unsubscribe");
using (Xs.CombineLatest(Ys, (x, y) => x + y).Timestamp().Subscribe(
z => Console.WriteLine("{0,3}: {1}", z.Value, z.Timestamp),
() => Console.WriteLine("Completed, press a key")))
{
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
100: 11/12/2009 12:17:45
101: 11/12/2009 12:17:46
102: 11/12/2009 12:17:47
103: 11/12/2009 12:17:48
104: 11/12/2009 12:17:49
105: 11/12/2009 12:17:50
106: 11/12/2009 12:17:51
107: 11/12/2009 12:17:52
108: 11/12/2009 12:17:53
Concat - cold observable
class ConcatCold
{
private static IObservable<int> Xs
{
get { return Generate(0, new List<int> {0, 1, 1}); }
}
private static IObservable<int> Ys
{
get { return Generate(100, new List<int> {1, 1, 1}); }
}
// same Generate() method as above for Merge...
private static void Main()
{
Console.WriteLine("Press any key to unsubscribe");
Console.WriteLine(DateTime.Now);
using (Xs.Concat(Ys).Timestamp().Subscribe(
z => Console.WriteLine("{0,3}: {1}", z.Value, z.Timestamp),
() => Console.WriteLine("Completed, press a key")))
{
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
0: 11/12/2009 12:17:45
1: 11/12/2009 12:17:46
2: 11/12/2009 12:17:47
100: 11/12/2009 12:17:48
101: 11/12/2009 12:17:49
102: 11/12/2009 12:17:50
Concat - hot observable
class ConcatHot
{
private static IObservable<int> Xs
{
get { return Generate(0, new List<int> {0, 1, 1}); }
}
private static IObservable<int> Ys
{
get { return Generate(100, new List<int> {1, 1, 1}).Publish(); }
}
// same Generate() method as above for Merge...
private static void Main()
{
Console.WriteLine("Press any key to unsubscribe");
Console.WriteLine(DateTime.Now);
using (Xs.Concat(Ys).Timestamp().Subscribe(
z => Console.WriteLine("{0,3}: {1}", z.Value, z.Timestamp),
() => Console.WriteLine("Completed, press a key")))
{
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
result
0: 11/12/2009 12:17:45
1: 11/12/2009 12:17:46
2: 11/12/2009 12:17:47
102: 11/12/2009 12:17:48
让类支持IObservable<T>
If you are about to build new system, you could consider using just IObservable<T>.
Use Subject<T> as backend for IObservable<T>
class UseSubject
{
public class Order
{
private DateTime? _paidDate;
private readonly Subject<Order> _paidSubj = new Subject<Order>();
public IObservable<Order> Paid { get { return _paidSubj.AsObservable(); } }
public void MarkPaid(DateTime paidDate)
{
_paidDate = paidDate;
_paidSubj.OnNext(this); // Raise PAID event
}
}
private static void Main()
{
var order = new Order();
order.Paid.Subscribe(_ => Console.WriteLine("Paid")); // Subscribe
order.MarkPaid(DateTime.Now);
}
}