今天要介紹IObservable-IEnumerable-Task這三者的關係
IEnumerable代表的是一組資料序列,而IObservable可以代表一個”可被觀察的資料序列”,可以觀察下面兩段code,在通過ToObservable()後可以將集合轉換,所以1與2印出來的結果是相同的。
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 1
list.Select(x => 10 * x).ToList().ForEach(Console.WriteLine);
// 2
list.ToObservable().Select(x => 10 * x).Subscribe(Console.WriteLine);
這邊舉個例子,我知道天線寶寶在太陽出來的時候會說你好,但是我並不知道什麼時候會發生,所以我將天線寶寶說你好這個Task轉換成可以被觀察的事件,當事件發生的時候就會印出來讓我知道。
var teletubbiesSay = Task.FromResult("你好");
teletubbiesSay.ToObservable().Subscribe(Console.WriteLine);
既然我知道了IObservable-IEnumerable-Task三者之間是有關係的的,並且三者都有monad的特性,那我們可以怎麼使用呢?假設我今天有一個名單(IEnumerable),並且可以透過第三方的API來得知名單上人物的真實身份
var nameList = new List<string> { "洛伊德", "約兒", "安妮亞" };
var roleDic = ImmutableDictionary<string, string>.Empty.Add("洛伊德", "黃昏").Add("約兒", "睡美人").Add("安妮亞", "實驗體007");
// map
var result = from n in nameList
select GetRoleAsync(n).Result;
// 如果希望用await
// var observable = nameList.Select(async n => await GetRole(n));
observable.ToObservable().Subscribe(Console.WriteLine);
Task<string> GetRoleAsync(string name) => Task.FromResult(roleDic[name]);
我可以直接透過Map到結果得到每個人的身份,但是這邊需要用到執行緒封鎖的方式取值,如果將.Result
拿掉,或是改用await修飾詞,result的型別就會是IEnumerable<Task<string>>
,說實話真的不好處裡,而我們總是希望能夠不使用執行緒封鎖(說實話想用Rx.net你還真的最好封鎖)。但IObservable-IEnumerable-Task是三位一體的monad,所以我可以用SelectMany輕易解決此事
var observable =
from n in nameList.ToObservable()
from a in GetRoleAsync(n).ToObservable()
select a;
observable.Subscribe(Console.WriteLine);
在做這一章的時候踩到一個雷,在測試非同步的時候一直出bug,Test2一直印不出東西來
Test1().ToObservable().Subscribe(Console.WriteLine);
// 執行緒封鎖
Test2().ToObservable().Subscribe(Console.WriteLine);
// ...
async Task<string> Test1()
{
Task.Delay(100).GetAwaiter().GetResulter();
return "執行緒封鎖";
}
async Task<string> Test2()
{
await Task.Delay(100);
return "不封鎖";
}
後來發現Rx.net底層用的是封鎖的方法,所以Test2看到await控制權就轉移,然後主程式就結束了,永遠等不到結果……,為了解決問題GitHub Repo上有了一個新的資料夾AsyncRx.net,正式發布不知道還要等多久,詳細可以參考這篇issue。