2013年を振り返る
- 2013-12-30
振り返るシリーズ第三弾。毎年、30日に振り返っているので、今年も30日で。ちなみに12/30は私の誕生日でして、ついに30歳を迎えてしまった……。20代さようなら、いや、別にいいんですが、C#er若くない人サイドに入ったな!という感じなわけでして、新陳代謝がほげもげとか。
2011年はMVP受賞、2012年はgloopsへの入社と退社(在籍期間たったの10ヶ月だった!)、というわけですが、では今年のメイントピックは、やはり当然グラニ a.k.a.謎社の始動です。2012年末の段階では
今はニート。ではなく、謎社にいます。謎社ってなんだよというか伏せる意味は特にないんですが、まぁまだ伏せておきます。実際のとこ出来たばかりの会社でして、だいたいほぼほぼ創立メンバーとして働いてます。そして現在のところPHPが95%でC#が5%といったところですが(私もPHP書いてますよ!毎日吐き気が!)、直近の目標はC#比率を高めることです(笑)
来年は変化というよりは進化、↑で書いたとおりにゲームを、じゃあなくて会社を前身させるのに全力で突き進む、というわっかりやすい目標があるんで、そのとーりに邁進しましょう。C#といったら謎社!みたいな、C#を使う人が憧れるぐらいな立ち位置の会社にできればいいなと思っています。
という話を立てていたわけですが、どうでしょう?かなりの部分で達成出来たのではないかと思います。会社は信じられないぐらいの成功を果たしていますし(※別に私の力ではなくてメンバー全員の力の賜物です)、当初PHPで書かれていたプログラムは、100%、C#へのリプレイスを果たしました。ボロ一軒家(リリース前は会社=一軒家にすし詰めで開発してた)でPHP書いてる時から、こういったヴィジョンを描いていたし、1年のうちに実現しきったのは、相当やった方だと年の末ぐらいは自画自賛させてくださいな。
といっても、まだ「C#を使う人が憧れるぐらいな立ち位置の会社」になれているかといったら、知名度であったり、そしてまだまだ実績も足りていないので、全然、目指す水準には満たしていないです。まだ、やっと0→1に、スタート地点に立ったばかり。ここからは1→100にしていかなければならない。また、外向きだけではなく、内側もまた全然整備しきれてないので、働く人がここで働くことに満足できる状態を作れなきゃとか、やることは山積み。
C#
今年のブログ内容は一気に非同期に傾いています。というのも会社で本格的にasyncを導入して使いまくり始めたこともあって、実践的に地雷を踏みまくってノウハウが溜まったからです。こういう実践的な話は、リファレンス情報だけではどうしても足りないわけで、両方が必要なのです。よく、C#はMSDNに情報がいっぱいあって、こんなに充実している言語、他にないよ!何が不満なの!?というけれど、半分合ってて、実態としては全然あってない。あくまで実践的な情報が重要度では第一、リファレンスは補完するもの。だから不満に感じるのは当然です。Microsoftとしては、そういう不足はコードレシピで補いたいようだけれど、それじゃ補えないというか、結局こういうのってリファレンス側に近い情報であって、欠落を埋められるわけがない。
じゃあ誰が埋めるのか、埋められるのかって、それは私達自身だけでしょう。自らの知見から来る情報がネットに溢れるといいな、と思っていますし、そのためにもまず自分たちがやっていきます。特に.NET界隈は実地的な話がなくて。海外とのレベル格差も酷い。圧倒的に日本はレベルが低い、ように見えてしまう。実際は優れた人は表に出てこないとか、絶対量が違うからとか、幾らでも言い分もあるし、確かにそうなのでしょう。でも、やっぱり、見えなければ意味がないし、その結果が、これ。C#という言語の他言語に比べた地位の低さ。例えばですよ、AWSは沢山の実地的な話が溢れてる。かたやAzureは、ただのリファレンス情報が垂れ流されているだけ。こういうの地味にきっついし差として現れるんだよね。C#も同じようなものですよ、現状。残念ながら。
というわけで超積極的に、情報は出していきたいのですにぇ。
さて、個人的には相変わらず小さなライブラリは作りまくってました。NuGetの登録数も36になりました。そういえばAsyncOAuthも今年からですね、おかげ様でOAuthライブラリといったらこれだよね!ぐらいに受け入れてもらえたようで、色々なところで使われているようです。謎社自身でもがしがし使ってます。
今年もう一つ、注力していたのはCloudStructuresというRedisクライアントですね。C# + Redis、しかもC# 5.0推奨というハードルの高さすぎてあまり使われてる感はないですが、これは謎社でハイパー使ってます。
AsyncOAuthやCloudStructuresは、謎社でのPHP→C#移行で絶対必要になるパーツという目算だったので先行して仕上げていました。その路線に乗っかって、来年育てていくのはLightNodeですね。こういう技術、必要になってから調べ始めているのでは遅く、でもあまり長いスパンで見ていてもしょうがない。研究開発機関ではないのだから。というわけで、今のところ、半歩先ぐらいを見据えて動いています。
ハイパー放置状態のlinq.jsを毎年何とかするする詐欺は、来年こそ、は……。
会社
今では当たり前のようにC#の企業ですが、当初はPHPでした。6/8時点で講演した.NET最先端技術によるハイパフォーマンスウェブアプリケーションは、はてブ300以上、現時点でViewsが33500と、.NET系にしてはクリティカルヒットを飛ばしましたが、スライドの冒頭で紹介したとおり、この時点ではPHPだったのです。実際にリプレースが完了したのは7/16日。深夜メンテナンス時間を挟んで朝に一気に切り替え。若干のトラブルはあったものの無事完了。
今年最大のハイライトであり、というか私自身も今のところ生きてて一番のハイライトですね。出来てまもない会社だから、体力も人員もない、でも凄い上り調子だからアプリを止めるとかありえないどころか育てなければならない。そんな中でリソース分散してPHPとC#を並走させて、統合、そして切り替え。まず、やるって決定が常識的に考えてありえないほど無茶苦茶だし、実際にやりきったのも凄いなぁと自画自賛Part2。やれるはずっていう机上の空論と、実際にやりきるってのは、違いはないけど必要な体力と精神力が全然違いますね、もうほんとヤバかった、特に精神的に……。
これに関しては、メンバーに恵まれました。出来たばかりの先行き不明な会社の、しょぼいホームページに、たった二行で求人情報が書かれててmailtoで送れ、というような状況で、素晴らしい人が次々と来てくれたのは信じられない話で。ほんと感謝の念に尽きません。
逆に今のほうが求人に苦戦しているのがアレ。あ、そんなわけでWe're Awaitingということで積極的に採用はしているのでいつでも歓迎ですよ - 求人ページ ← 未だにサイトしょぼい
職種はCTOなのですけれど、CTO論をぶったりは、しません特に。色々なところにボーダーがあって、そこを超えそうになったら発言するようにしているのと、つまり超えない時は何もやってないように見えて実際何もやってない!こたぁない。って感じですかね(テキトー)。まぁ、かくあるべきみたいなものはあります、私の中で、ちゃんと。どんぐらい全うしてるかというと、うーん、70点?
技術的な方向性はシンプルに最先端のC#、なわけですけれど、それが良いことなのかどーか。勿論、良いことです。でも必ずしも良いことと言えるわけじゃあない。じゃあ良いことに変えればいいだけです。こんなの単純な話で、よーは会社として保守的にならないことがバリューを産み出せるようになりゃあいいだけです。逆に、何も産み出せないならNGでしょうね。そうならないように何が出来るかを行動していくべきでしょう。
ゲーム
Super Hexagon最高!これに尽きる。マジ最高。これはヤヴァい。Windows Phoneの最大の欠点はSuper Hexagonがプレイできないことと断言していい。絶対無理ー、と思ったHYPER HEXAGONESTをクリアした瞬間とか、ゲームの達成感の全てが詰まってた。超興奮。数年ぶりにゲームの面白さを思い出させてくれたマスターピース。
というわけで、今年は圧倒的に超えられない壁にSuper Hexagonが存在しているのですが、次点としてはGAMELOFTのモバイル用レースゲーム、Asphalt 8かな。グラフィックのケレン味が実にモバイル向けで、スマートフォン向けゲームとしては現時点で最高のグラフィック品質。ゲームのほうも大味、じゃなくてダイナミックで楽しい。通信対戦もあるしね。
そしてWindows Store App版もある!しかもWindows Phone 8版もある!しかもWindows Store App - Windows Phone 8間での通信対戦が可能!(iPhone-AndroidとかiPhone-WP8とかは対戦不可)。そんなわけで社内Windowsクラスタで話題騒然、Surface持っているならマストバイ、とか微妙な盛り上がりを見せました。ていうか社内で普通にWindows Phone 8の実機が集まるところがまずアレ。
本とか映画とか漫画とかアニメとか
見てない!ゲームと同じく、この辺りも年々見なくなっていってますが今年は特に一番見てない気がする、忙しさのせいかな(言い訳)。引っ越しして、実家に預けていた本とか漫画を全部回収したのだけど、あー、学生の頃は、いっぱい読んでるわけでもないけれどまぁまぁ読んでたなぁ、とか寂しくなったりはしたりして。
来年
ここ数年は、毎年ジェットコースター状態で目まぐるしく変化していて。けれど、大きな目標からはブレないで、年々近づけている気がします。一番最初に若くない人サイドに入ったとか、新陳代謝とか言いましたが、来年はそういうことが起こる状態を作っていきたいですね。C#が、若い人がこぞって使うような言語になってればいい、と。そのためにできること。人がすぐに思い浮かべられる、メジャーなアプリケーションの創出と、C#による圧倒的な成果、C#だからこその強さ、というのを現実に示していくこと。雇用の創出、の連鎖。
というわけで、来年も引き続きご期待くださいだし、よろしくお願いします。
LightNode - Owinで構築するMicro RPC/REST Framework
- 2013-12-23
LightNodeというMicro RPC/REST FrameworkをOwinで作りました。というわけで、LightNodeについて……の前に、そもそもOwinって何?という感じだと思いますので、作成物を通してOwinが開くC#によるウェブ開発の未来について、もしくはOne ASP.NETというヴィジョンが見せる世界についてお伝えしようかな、と。これはOne ASP.NET Advent Calendar 2013への記事ですしね!ちなみに副題は「OWINでハイパー俺々フレームワーク作成」。きゃうん。
LightNode
バージョンはまだ0.1です。急ぎで作ったので、そう完成度高くないです。とはいえ十分動きますし、これは来年育てていきたいと思っているフレームワークです。やる気は、かなりあります。半年後ぐらいには実用になってるかなあ、と。ソースコードとか課題管理はGitHubで。
例によってインストールはNuGetから。
- Install-Package LightNode.Server
細かいパッケージが実はいっぱいあったりして……。
- Install-Package LightNode.Client.PCL.T4
- Install-Package LightNode.Formatter.JsonNet
- Install-Package LightNode.Formatter.ProtoBuf
- Install-Package LightNode.Formatter.MsgPack
LightNodeが提供するのはサーバーサイドフレームワーク(競合はASP.NET Web APIです)と、クライアントサイドのAPIアクセスコード自動生成(WCFがやっているような!)、両方です。クライアントサイドの生成は、Unity3Dへのコード生成が最初のターゲットだったはずなんですが時間的な都合上、今はPCLだけ、です。まあ近いうちにはUnityのは出します、あとTypeScript用のも。
目標はクライアントサイドからサーバーサイドまで全てC#で統一されることによる生産性の超拡張を具現化すること。クライアントがUnityでサーバーがOwinで全部C#、みたいな、ね。両方C#で作り上げられることによるメリットを最大限引き出すことを目指しています。また、JSONオンリーではなくMessagePackやProtocol Buffersでのやり取りも可能なように、パフォーマンスを最大限追求します。また、そのうえで他言語との通信も捨てない、というわけでHTTPでRESTなでほげもげは捨てず、他言語からもサーバーへは自由にアクセス可能です。
逆にRESTfulでビューティフォーなURL設計とかは優先度ゼロなので完全に捨てています。
Lightweight as a Server
LightNodeは超絶Lightweightなフレームワークです。何がLightweightかというと、パフォーマンスと実装の簡単さ、両方を指して言ってます。特に実装の手間はほとんどないぐらい非常に軽量です、ASP.NET Web APIとか超重量級ですからね(それはさすがにいいすぎ)。
サーバーはOwin上に構築されていますので、まずOwinMiddlewareのセットアップが必要です。コンフィグだけは少し書いて下さい。SelfHostでもIISでもいいので、どちらかのOwinホストパッケージをNuGetで引っ張ってきて、スタートアップクラスでUseLightNodeする。
// OwinのStartup
public class Startup
{
public void Configuration(Owin.IAppBuilder app)
{
// 受けつけるVerbを決めたりデフォのTypeFormatter(複数も当然できる)設定したり
app.UseLightNode(new LightNodeOptions(
AcceptVerbs.Get | AcceptVerbs.Post,
new JavaScriptContentTypeFormatter()));
}
}
準備はこれだけ。で、実際にAPIはどうやって作るかというと、LightNodeContractを継承したクラスのパブリックメソッドが、自動的にAPIとして公開されます。
// LightNodeContractを実装すると全てのpublicメソッドがAPIになる
// URLは {ClassName}/{MethodName} で固定
// この場合だと例えば http://localhost/My/Echo?x=test
public class My : LightNodeContract
{
// 戻り値は↑で設定したContentTypeFormatterでシリアライズされて渡る
public string Echo(string x)
{
return x;
}
// 今時なのでasyncもサポートしてるよ!戻り値はvoid, T, Task, Task<T>が使えます、ようは全部。
// パラメータのほうは配列、Nullable、オプション引数あたりはOK
public Task<int> Sum(int x, int? y, int z = 1000)
{
return Task.Run(() => x + y.Value + z);
}
}
これで、「http://localhost/My/Echo?str=hoge」で叩けるってことになります。URLは {ClassName}/{MethodName} の形式で完全に統一されて、カスタマイズの余地はありません。
サーバー側は基本的にこれだけです。単純!地味!
必要最小限のラインってどこかなぁ、というのを考えた時、ここになるかな、と。ルーティングやパラメータのバインディング、レスポンスへの戻り値の書き込みなどはフレームワークがやってくれなきゃ死ぬけれど、それ以上はない。これだけでも割と十分便利に使える、の限界ラインを狙って、極力、機能を削ぎ落とす形で取捨選択しています。ちょっと不便、なぐらいで存外良かったりするのですよ、ちょっと便利、のために色々なものが引っ張られるより100倍良いでしょう?
あと私は「設定より規約」って嫌いなんですよね。別にXML Hellがいいとは言わないですが、あのやり方はLL向けかなあ、という気が相当してまして、C#でそれをやっても嬉しいところってあんまないんじゃないかって思います。属性とか型をどういう活かすか、のほうがいいとオモイマス。
Lightweight as a Client
純粋(?)なRESTって、C#でも、他のどの言語でも、決して扱いやすいわけじゃない。だからラップしたHogeClientを作りますよね。そして、そうした特化したRestClientの作成って、結構難しい。使いやすいClientって中々作れるものじゃあないです。手間がかかるうえに使いにくいものが出来上がるなら、絶望的です。だからサーバーAPIとクライアント、自分たちで両方を作る時、もんのすごく苦労してしまう。どこもLightweightじゃない。こんなことならSOAPでVisual Studioで自動生成してくれてるののほうが100億倍Lightweightだったよー、とかね、それはそれで事実です。
そこでLightNodeは真のLightweightを提供します。自動生成するからコストゼロで完璧なClient SDKが手渡されます。
// 中身はHttpClientなので当然全部async
// メソッドは全て
// client.{ClassName}.{MethodName}Async({parameter}) で生成されます
var client = new LightNodeClient("http://localhost");
await client.Me.EchoAsync("test");
var sum = await client.Me.SumAsync(1, 10, 100);
C#クライアントにとって、自然な操作感でサーバーサイドへとアクセスし、戻り値を受け取ることが出来ます(複雑なオブジェクトは内部のシリアライザを通して自動変換されます)。クライアント側にとってはRPCのように、サーバーを意識せず透過的にやり取り可能なこと、を目指しました。
この自動生成コードは、HttpClientを使ったRestClientとしては、割とイイ感じに出力するので、そういったのの参考にもどうぞ多分。REST APIはこういった形にラップされてるのが使いやすいと思ってるんですね、私は。インターフェイスの明示的実装の活用例。手作業だと面倒でサボッてしまいがちなCancellationTokenも受け取り可能になってたり、その辺は機械生成ならではの徹底さです。
ちなみに現状は実装時間的都合でまだPOSTにしか対応してない(次のアップデートでGETにも対応させます……)。
Micro RPC/REST Framework
Micro RPC FrameworkないしMicro REST Frameworkというのは造語です。ググッてもさして検索結果には出てきません。とはいえ、言わんとすることは分かるのではないでしょうかしらん。ヘヴィ級ORMのEntity Frameworkに対する、機能最小限でコンパクトなDapper。みたいなものです。徹底的に削ぎ落としたREST Framework。対極にあるのはUltra Super HeavyなFramework、って何?というと、ASP.NET Web APIかな。そう、ASP.NET Web APIって、別にLightweightじゃないよね?と、ずっと思っていて。ずっとしっくりこなくて。
というか既存のRESTなフレームワークってどれもLightweightに思えない。何が自分の求めているものなのかなあってずっと考えていたのだけれど(その間、会う人会う人にWeb APIってしっくりこないんです!と吹っかけて回ってた、どうもご迷惑おかけしました)、RPCだ!って至りまして。一周回ってRPC、これはアリだ、と。
REST vs SOAP, REST vs RPC, REST vs WCF
そもそも対立軸がオカシイ。そして、その結果、orになるんだよね、どちらを選びますか?って。それ以外がないの。なんでそう極端な対立になってしまうの?でも、しかし、それはある意味正しい。だって何かを作るには、この世にあるものから選ぶしかないのだから。ヘヴィなSOAPが嫌ならRESTしかなく、ヘヴィなRPCが嫌ならRESTしかなく、ヘヴィなWCFが嫌ならREST(ASP.NET Web API)しかない。
でも、本来は選択肢もっとあって良かったはずなんだよね。どうして中間がなかったんだろうね。そんなにRESTfulは素晴らしく輝かしい未来だったのかな。あまりにも、SOAPが、WCFが辛すぎて反動で極端に振れるしかなかったのかな。
RESTful
どうでもいい。だからLightNodeはGETとPOSTしかありません。
XML/JSON/XXX-RPC
doudemoii。入/出力がフォーマットに固定されるのが世の中的に厳しい。XMLは今どきアリエナイといわれてもshoganai感じになってきてしまっているし、その他のバイナリ形式もJavaScriptで扱いにくくなったりして絶望感ある。JSON最強はありますけど、それはそれで、一部クライアントとはMsgPackとかProtobufとかで高速省スペースな通信したいって欲求には応えられない。仕様もあってないようなものだし、それらに従っていいこと、あまりない。
Language Interoperability
LightNodeはかなりC#に依存というか、むしろ尻尾から先頭までC#で一気通貫して通せることをメリットの一つとしています。とはいえ(広義の)RESTなので、HTTPでGETかPOSTでアドレス叩けば結果帰ってきます。他の言語からも叩けるって物凄く大事なので、いくら一気通貫、C#で大統一理論を正義にしていても、大事にしてあげたいです。JavaScript無視するとか自殺行為ですしね(TypeScriptコードの生成は将来的に作りたいものの一つです)。
仕様は、URLは{ClassName}/{MethodName}、パラメータはGETはクエリストリング、POSTはx-www-form-urlencodedで送ります。そのためということもあって、基本的にパラメータの型には制限があって、基本型(intとかstringとかDateTimeとか)のnullableとarray、それとオプション引数までにしか対応していません。複雑な型はダメ。
ダメな理由としては、あと、それ許可するとメソッドや引数がAPIドキュメントの代わりにならないんですよね。何を渡すことが許されるているのか、のシンプルさが消える。せっかくC#側で作ることの良さ、型があること、を消してしまうほうがmottainai、トレードオフとしてナシという判断です。そしてそのほうが言語間Interoperabilityにも有利ですし。
レスポンスのほうは自由です。何でもありです。基本的にbodyに書かれるだけなので、シリアライズ可能なものならなんでもOK。シリアライザも自由に選べます。こういった形式が自由なのは、パフォーマンスのためです。C#でガリガリに速くしたいなら、やっぱProtobufやMsgPackだろう、と(バイナリだから単純に高速省スペースとかいうのはただの幻想なのでWCFをそういう目では見ないようにしましょう)。でもJSONで吐けないのはそれはそれでありえないわけで、自由に選べる、かつ共存できるように(拡張子やContent-Typeで識別します)しています。
RPC風であり、REST風な中間点がこれかなあ、と。これなら俺々仕様っぽさは特になくRESTといって納得できるレベルに収まってるかと。そのうえで、クライアント側的にはRPC風に使えるのでシームレス感が相当ある。APIの構造がC#に引っ張られて、他言語からキモチワルイ感を醸しだしてしまう可能性はあるのですが(但しメソッド名のcamel,Pascalは自由でどちらでも通るようになってます)、こればっかりはshoganaiかなあ。
そもそもREST的な公開されてるほげもげって各言語、どの言語でも決して使いやすくはないような。だからSDKでラップしたものを使うでしょう?言語中立で万歳、みたいな理想世界がない以上は、プライマリの言語での使いやすさ+セカンダリ以降でも可能な限り使いやすさを維持できる構造、にするのがベターかなあ、って。思ってます。
Why Code Generation? Why not Dynamic Proxy?
今のクライアントコードは、T4によるソースコード生成になっています。正直ダサい。クライアント側はソースコード生成よりも、共通のインターフェイスに対して動的コード生成でProxy作ってやるほうが手軽に扱えていいのよね。どういうイメージかと言いますと、例えば
// こういうインターフェイスがサーバー側とクライアント側が共に参照するDLLに定義してあって
public interface IHoge
{
int Sum(int x, int y);
}
// サーバー側は↑のインターフェイスを実装する
public class HogeContract : IHoge
{
public int Sum(int x, int y)
{
return x + y;
}
}
// クライアント側は↓のような形で使える
// Createの戻り値がIHogeになってて、その実装は動的生成されたもの、という感じ
var sum = LightNodeClient.Create<IHoge>("http://localhost").Sum(10, 20);
実にスッキリしていいですね!クライアントサイドのIHogeの実装は、動的コード生成により実行時に挿入されるので一切、手を加える必要はありません。ちなみに実装方法はAssemblyBuilderを使ってひたすらILゴリゴリです。ExpressionTreeのCompileToMethodは静的メソッドしか作れないので、↑のイメージのようなインスタンスメソッドへの生成は気合入れて書くしかないのですねえ、やれやれ……。
でも、今回はソースコード生成にしました。それはIL書くのが面倒だから、ではなくて(実際面倒だからってのはちょっとありますが!)、理由はそれなりに幾つかあります。
まず、インターフェイスの戻り値=クライアントにとっての戻り値、じゃあなくなってます。具体的にはTaskです。非同期以降の世界ではクライアント側の型はTask以外はありえないんです。ここで、じゃあインターフェイス側もTaskを強要すればいい、ってのは、それは不便なのでナシですしねえ。クライアント側のメソッド名はXxxAsyncにしたいとかってのもありますし、やっぱ、現代においてはインターフェイスをきっちり一致させるというのは難しい。
あと、Unity。まあ、何度か名前↑で出しているようにUnityはかなりターゲットなわけですが、UnityのC#ってバージョン古いのですよね、Taskなんてないんですよ……。そんなわけで各プラットフォーム毎に全然違う生成したほうがいいってことになってしまいますよねえ、と。C#以外にTypeScriptなんかもターゲットにしたいですしね。
そして最後に、AssemblyBuilderはフル.NET Frameworkにしかない。WinRTやPhone、当然PCLにはない。ないないないないなので、手間隙かけてIL書いてもあんま嬉しくなれない。
そんなわけで、ソースコード生成を手法に選んでいます。
とはいえ、提供手段がT4であることが良いかどうかはビミョイところですね。こういうの自体は、別に割とあるパターンではあるのですけど、例えばPetaPocoやORM LiteなどMicro ORM系はEFなどのヘヴィーなデザイナの代わりとしてT4を用いているし、 T4 MVCとかもあるし、……、うーん、そのぐらいか。あんまないね。
あと今の実装はdllをロードしてそれを解析するんですが、ロードしたあとそのまんまアセンブリ掴みっぱなしで解放されないから、解放するにはVS再起動しないといけないとかいうクソ仕様とかも残ってるので、何とかしなきゃ度は相当高いです。誰か解決策教えてください。
Performance
機能面では最小な上に(劣る、とは言いません)、わざわざ新しく作る以上、パフォーマンスで負けていたら馬鹿みたいな話です。というわけで結果。
OWIN上のWeb API、OWIN上のLightNode、OWIN上の生app.Run、あとふつーにIISでホストする生HttpHandlerの4つでテキトーに測ってみました。Nancyは加えようと思ったんですがちょっと動かなくて調べる時間がなかったので(この記事はAdvent Calendar的にギリギリで書き上げているのです!)いったんナシ。
んで、速いです。というかほっとんど生HttpHandlerと変わらない速度出せてます。そりゃ機能少ないんだから当たり前……、ではないです。機能が少ない=速い、に直接結びつくほど世の中、甘くはありません!この手のものを作るにあたって速度を稼ぐポイントは幾つかあって、しっかりポイント抑えたコード生成(&キャッシュ)をしつつ、余計な要素を足さないことで最速になります。そりゃそーだ。ともあれ、これ以上は速くならないという限界ラインを突いてます。これより先はどう頑張っても誤差範囲は超えないでしょう、というか生Handler近辺の時点で、もう大して変えられんです。
その辺の実装のコツのお話はまた次回にでも。(ただEnum周りのマッピング処理が現在ゴミなのでEnum入れると遅いです、これは次回までに改善します)
Owin
ASP.NET Web APIがOwin対応とか、そういうのどーでもいーんだよね。だってIISにホストするでしょ?SelfHostとか別になくてもいいレベルでしょ?プロダクション環境では使わないでしょ?というわけで、あるものを使うという点では、別に今はOwin対応とかドウデモイイレベルの話です。皆が今Owinにさして興味持てなかったり使い道に想像沸かないとしても、そりゃそうだ、です。だってIISでいいんですもの。
Owinの利点はMiddlewareを組み合わせられること。けれど現状は、多様なMiddlewareは、特にはない。できたてほやほやみたいなものだから。むしろASP.NET Web APIやASP.NET MVCレベルでのコンポーネントのほうがあるし、将来的にもきっとそうでしょう。つまり、Middlewareも利点だー!と声高に言ってもshoganaiところがある。
でも、それでも、そこに未来はある。Owinは誰もが簡単にMiddlewareを作れる。小さなちょっとしたユーティリティから、大きいフレームワークまで。ついに始まった自由の世界。多様なMiddlewareは、今は、特にはない。でも、作ればいい、必ず彩り豊かになる。そうなればASP.NET Web APIのOwin対応なども、意味がでてくる。
そしてパフォーマンスですら手に入る。ああ、パフォーマンスは大事だ、そう、本当は大事でなかったとしても、とにかくキャッチーだからね。今までのASP.NETコアランタイム、System.Webがヘヴィだとしたら、それを完全にバイパスして直繋ぎしたら。発表されたHelios IIS Owin Web Server Hostは驚異的なパフォーマンスを見せている。なるほど、すごく魅力的に見える。なにより、Microsoftは本気なんだなって気がする。Helios自体はまだαだけど、今はSystem.Webにホストしてもらって、Heliosが完成したらそっちでホストすればいい。そこが選べるのもOwinのいいところだ。ああ!素晴らしいじゃないか、Owin!
Create Your Own Framework
俺々フレームワークは悪。常識です。常識。かといって、何もかも作らないわけにはいきません。何を作り、作らないか、その見極めが戦略として非常に大事。自分の戦略でもそうだし会社だったらなお大事。
さて、今回は作ったわけですけれど、その理由は単純にないから。ないものは作る。当たり前だよにぇ。といっても何もかもを満たすものなんて存在しないので、妥協できるかどうかのラインを見定めるってことではあるのだけれど。妥協ラインですが、C#の場合って、Microsoftで完結するものなら凄く整ってるんですよね、妥協OKというかむしろ完璧すぎるぐらいに。でも、今回の需要はMicrosoftの外側、Unityとか他のクライアント系のとか、それらと一気通貫に繋がって欲しいって需要なのです。Microsoftの中で完結してそれ以外とは疎結合、じゃなくて、繋がれる範囲は可能な限り全開に密結合して欲しいってのがリクエスト。そういうのって、未来永劫Microsoftから出てくることはない。絶対に。だから、作るって結論になる。
あともう一つはどのぐらいのクオリティで作れるか。作ったはいいけどクソクオリティだったら不幸になるだけだからね!そして、C#の場合はVisual Studioとの統合具合もかなり大事。だから、MVCフレームワークなどだと、単純に作業量が超絶多くて全体のクオリティを保つのは非常に大変なうえに、ASP.NET MVCはVS統合が進んでてサクサクViewとControllerを相互に移動出来たりコンパイルエラーがくっついてたり、そういうところまで面倒見るのは不可能に近い。だから、部分的に良い物を作れたとしても全体的には超えるのって凄く難しいから、俺々フレームワークは、あまり良い選択肢にはなれなさそう(でもNancyとか頑張って欲しい!)。
Service系のフレームワークだとViewとかとの面倒みなくていいしVS統合もそんなに気を配らなくていい(WCFぐらいパーフェクトな統合があればそりゃ素敵だけど、WCFは統合されてはいても他に問題だらけなので除外)、最小限の機能のラインが見えていて、かなり満たしやすい。性能だって少し頑張れば既存のものを抜くのも簡単。そんなわけで作るのはアリだ、のラインに個人的には達しました。
Owin EcoSystem
Service系ならば、そもそもHTTPに乗らなくてもいいじゃない?特にパフォーマンス優先なら!という選択もありますね。それを選ばないのは、エコシステム。サーバー側には沢山のノウハウやシステムがあり、何もしなくても最高のInteroperabilityがある。通信関連ではHTTPったら最強ね。っていうのは揺るがない。よほどパフォーマンス優先な根幹的な何かを作るのでなければ。
そして、Owinもまた理由になります。今までの俺々フレームワークの最大の欠点は、全て自前で作るしかなかったことです。でもOwinがあれば違う。認証?他のMiddlewareで。パフォーマンスモニタ系?例えばGlimpseは最高のモニタライブラリだけど、俺々フレームワークで、こういうのが一切使えなくなるって、痛手というか、それだけでありえないレベルになりますよね。でも、Owinならば、GlimpseがOwinに対応すればそれだけで乗っかることが出来る(そして実際、現在対応作業中のようです)。New Relicのような監視ツールなどもそう、俺々フレームワークであっても、そういうのにフルに乗っかっていけるってのが、今までと違うところだし、だから、作ってもOKの許容ラインに達しやすくなったと思いますですよ。
私も、LightNodeのようなフレームワークレベルのものだけじゃなく、他のフレームワークで使える小さなMiddlewareをこっそり作って公開してたりします。一つはOwinRequestScopeContextで、HttpContext.CurrentのようなものをOwin上で使えるようにするもの。もう一つはRedisSessionで、その名の通り、裏側がRedisのセッションストアです。RedisのHash構造に格納していて、リクエスト開始時に全部のデータを読み込み、リクエスト実行中のアクセスは全てインメモリで完結さえ、リクエスト終了時に変更があったもの差分だけを書き出す(RedisのHash構造だからこそ可能)ようにしています。実はこれの原型は既に謎社で実稼働していて、沢山のアクセスを捌いている実績アリだったりして。
今後RubyのRackにある便利Middlewareが移植されたりとかもするんじゃないでしょうか、むしろ良さそーな発想のものは自分達で移植してみるのもいいかもしれません。Owinが出たことで、自分達で作ることが、独善じゃなく発展の道になった。
One ASP.NET。You。使うだけじゃなく作る。それがこれからのASP.NETの未来だと思います。
Related Works
WCF。なんのかんのいってWCF。は偉いねえ、壮大だねえ、とか。LightNodeはWCFのABCからBindingを抜いたようなイメージでいいですよ。で、やっぱWCFとかの、その手の抽象化は辛い!何か被せて共通化して出来た気がするのは誰も満足させられないパターン。
rpcoder。Aimingさんの、独自IDL(Interface Definition Language)からUnity用のC#コードとかを吐き出すもの。LightNodeとの違いは、IDLかどうか、かしらん。LightNodeはIDLじゃなくてサーバーサイドの実装そのものが定義になるので、そういった外部定義不要なので、手間削減と、実装との乖離が絶対にないってとこかしらん。
似たようなというか定義という点ではRAMLとかね、まぁRAMLは最悪かなぁって思うのですけれど。RESTfulの呪縛に囚われて極北まで行くとそうなるのかねえ。どうぞ素敵なモデリングをしてください。ほんとdoudemoii。
Google Cloud Endpoints。サーバーの実装があって、そこからiOSやAndroid用のコードを生成するってもの。いいですねー、これですよこれ。Cloud Endpointsの正式リリースはついこないだですが、(特に)モバイル向けのバックエンドはこういうのがベストだと本当に思いますし、RPCの時代というかそういったようなものの時代への揺り戻しというか、再び多様性の時代が来たかな、と、健全で素敵です。
ServiceStack。これは、WCF Alternativeの中では一番メジャーな選択肢、ではあるのだけど、正直、なんか、この人のAPIセンスは……。辛い。正直ナシです。ちなみにv4から有料化しました。
Finagle。Twitter製の、Scalaでできた非同期でプラガブルなRPCフレームワーク。非同期なので全部Future(C#のTaskみたいなもの)。Relatedといったけど特に直接的な影響はないけど、オサレでモダンなフレームワークがRPC、というところだけちょっと強調とか。
DuoVia.Http。Owinで動くLightweightのService Libraryということで、LightNodeに一番近い先行実装ですね!クライアント側はプロキシによる動的生成なので非同期なし。サーバー側がrefやoutに対応させたりとか多機能を狙いすぎて、実行速度が引っ張られてたりとか、ちょっと違うかな、と。
ASP.NET Web API。まぁ、散々腐しましたけれど、実際ふつーに選ぶのならASP.NET Web APIが最初の選択肢だと思います。悪くないですよむしろイイですよ。そもそもLightNodeの実装にあたっては50分で掴み取る ASP.NET Web API パターン&テクニックとかOne ASP.NET, OWIN & Katanaとかガン見してたので味噌先生には頭が上がらないのでWeb APIいいんじゃないでしょうか(適当)。真面目な話、ASP.NET Web APIが一番参考にしてるのは間違いないですので、話の流れ(?)で色々腐しましたが、良いと思いますよ、本当。
Conclusion
One ASP.NETと言いつつも別にフィーチャーされないYou!の部分を推してみました。人昔前は、こういった俺々フレームワークが乱立しないのが.NETの良さ、と言われていた、こともありました。ありました。過去の話です。世界の進化は速く、Microsoftだけが一手に全ての需要を引き受けられるわけがない。それぞれの需要に合わせて、時に組み合わせて、時に自分で作り上げることができる。そういった世界の幕開けがOwinです。まだまだMiddlewareは足りていないので、「組み立てる」にはならないでしょう、けれどそれを解決するためにも、自分達で作り、公開していきましょう?それがOpenな世界だし、これからのC#コミュニティのあるべき姿だと思っています。
(いつもやるやる詐欺で毎回言ってる気がしますが)LightNodeはコンセプトだけじゃなく、真面目に育てていきたいと思っています。そもそも、会社として、この辺の通信が来年は重要課題になってくるなあ、というのがあって考えてたものなので、諸々色々で半年後ぐらいには十分な完成度で掲示できるかなあ、って思いますですよ。勿論、皆さん今から使ってくれたら嬉しいですにぇ。
また、コンセプト語るには実装がなきゃ、と相当思っていまして。かつて人々は「パターン」「契約による設計」などアイデアに名前をつけて論じたけれど、 このごろの新しいアイデアはフレームワークやプログラミング言語、データベースエンジンなどを通じて表現されるようになった。 今は書籍ではなく実装が思想を表現する手段になっていると、Eric Evans(DDD本の人)は語った。そんなわけで、というわけではないですけれど、私は私の思想はコードで表現していきたいと思っているし、そもそもそうしてきた。linq.js(LINQが言語を超えることを)もChaining Assertion(流れるようなインターフェイスや英語的なるものの馬鹿らしさを)もReactiveProperty(全てが繋がるイメージを)もそうです。ライブラリは思想の塊なのです、言葉に出されていなければそこに思想はない?そんなことはなく、ずっと流暢に語ってくれるはず。
そしてC#の強さの証明は、会社の結果で表現していきます。実証されなければ何の意味もないし、何の説得力もない。誰に?というと、日本に、世界に。というわけで、引き続き来年の諸々にもご期待ください!
An Internal of LINQ to Objects
- 2013-12-16
というタイトルで、大阪で開催された第3回 LINQ勉強会で発表してきました。
大阪は初めてですね!というか、東京以外で発表しに行くのは初めてです。大阪遠い。
レベル感は、まぁもうLINQも初出から10年も経つわけだし(経ってない)、もはや初心者向けもないだろうということで、LINQ to ObjectsのDeep Diveなネタを突っ込んでおきました。こんなにまとまってる資料は世界にもないですよ!なんで知ってるかというと、linq.jsの実装で延々と何回も書いてるからです、はい。いいことです。そのぐらいにはパーフェクトな実装ということで。ver.3の完成は、も、もう少し、ま、まだ……。ごめんなさい。近いうちには、またベータ出すよ!←いい加減完成させろ
口頭で捕捉した内容としては、yield returnで書くメソッドは引数チェック用のと分離させよう、というところ。これ、メンドーくさかったらやらなくていいです。実際メンドウクサイですしね。コアライブラリっぽい位置づけのものだったらがっつしやるのも良いとは思いますが、普段からやるのはカッタルイでしょう。と、いっても、LINQ以降はあまり生でyield return使うこともないでしょうけれど。
イテレータの話とかは、実際doudemoiiんですが、気になる人は、これはそもそもC#の言語仕様書に書いてあります。言語仕様書はVSインストールディレクトリの
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#\Specifications\1041\CSharp Language Specification.docx
にあるので(日本語訳されてるよ!)、読んだことない人は眺めてみると楽しいんではかと思います。
非同期時代のLINQ
- 2013-12-04
この記事はC# Advent Calendar 2013の4日目となります。2012年はMemcachedTranscoder - C#のMemcached用シリアライザライブラリというクソニッチな記事で誰得でした(しかもその後、私自身もMemcached使ってないし)。その前、2011年はModern C# Programming Style Guide、うーん、もう2年前ですかぁ、Modernじゃないですねえ。2011年の時点ではC# 5.0はCTPでしたが、もう2013年、当然のようにC# 5.0 async/awaitを使いまくる時代です。変化は非常に大きくプログラミングスタイルも大きく変わりますが、特にコレクションの、LINQの取り扱いに癖があります。今回は、非同期時代においてLINQをどう使いこなしていくかを見ていきましょう。
Selectは非同期時代のForEach
これ超大事。これさえ掴んでもらえれば十二分です。さて、まず単純に、Selectで値を取り出す場合。
// こんな同期版と非同期版のメソッドがあるとする
static string GetName(int id)
{
return "HogeHoge:" + id;
}
static async Task<string> GetNameAsync(int id)
{
await Task.Delay(TimeSpan.FromMilliseconds(100)); // 適当に待機
return "HogeHoge:" + id;
}
// 以後idsと出てきたらこれのこと指してるとします
var ids = Enumerable.Range(1, 10);
// 同期バージョン
var names1 = ids.Select(x => new { Id = x, Name = GetName(x) }).ToArray();
// 非同期バージョン
var names2 = await Task.WhenAll(ids.Select(async x => new { Id = x, Name = await GetNameAsync(x) }));
ラムダ内でasyncを書き、結果はIEnumerable<Task<T>>となるので、配列に戻してやるためにTask.WhenAllとセットで使っていくのが基本となります。Task.WhenAllで包むのはあまりにも頻出なので、以下の様な拡張メソッドを定義するといいでしょう。
// こういう拡張メソッドを定義しておけば
public static class TaskEnumerableExtensions
{
public static Task WhenAll(this IEnumerable<Task> tasks)
{
return Task.WhenAll(tasks);
}
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
return Task.WhenAll(tasks);
}
}
// スッキリ書ける
var names2 = await ids.Select(async x => new { Id = x, Name = await GetNameAsync(x) }).WhenAll();
では、foreachは?
// 同期
foreach (var id in ids)
{
Console.WriteLine(GetName(id));
}
// 非同期
foreach (var id in ids)
{
Console.WriteLine(await GetNameAsync(id));
}
そりゃそーだ。……。おっと、しかしせっかく非同期なのに毎回待機してループしてたらMottaiなくない?GetNameAsyncは一回100ミリ秒かかっているから、100*10で1秒もかかってしまうんだ!ではどうするか、そこでSelectです。
// 同期(idsがList<int>だとする)
ids.ForEach(id =>
{
Console.WriteLine(GetName(id));
});
// 非同期
await ids.Select(async id =>
{
Console.WriteLine(await GetNameAsync(id));
})
.WhenAll();
ForEachの位置にSelect。ラムダ式中では戻り値を返していませんが、asyncなので、Taskを返していることになります(Task<T>ではなく)。同期ではvoidとなりLINQで扱えませんが、非同期におけるvoidのTaskは、Selectを通ります。あとはWhenAllで待機してやれば出来上がり。これは全て同時に走るので100msで完了します。10倍の高速化!
ただし、この場合処理順序は保証されません、同時に走っているので。例えばとある時はこうなりました。
HogeHoge:1
HogeHoge:10
HogeHoge:8
HogeHoge:7
HogeHoge:4
HogeHoge:2
HogeHoge:6
HogeHoge:3
HogeHoge:9
HogeHoge:5
処理順序を保証したいなら?WhenAll後に処理ループを回せばいいぢゃない。
// こうすれば全て並列でデータを取得したあと、取得順のままループを回せる
var data = await ids.Select(async id => new { Id = id, Name = await GetNameAsync(id) }).WhenAll();
foreach (var item in data)
{
Console.WriteLine(item.Name);
}
一旦、一気に詰めた(100ms)後に、再度回す(0ms)。これはアリです。そんなわけで、非同期時代のデータの処理方法は三択です。逐次await, ForEach代わりのSelect, 一気に配列に詰める。どれがイイということはないです、場合によって選べばいいでしょう。
ただ言えるのは、超大事なのは、Selectがキーであるということ、ForEachのような役割を担うこと。しっかり覚えてください。
非同期とLINQ、そしてプリロードについて
さて、SelectだけではただのForEachでLINQじゃない。LINQといったらWhereしてGroupByして、ほげ、もげ……。そんなわけでWhereしてみましょう?
// 非同期の ラムダ式 をデリゲート型 'System.Func<int,int,bool>' に変換できません。
// 非同期の ラムダ式 は void、Task、または Task<T> を返しますが、
// いずれも 'System.Func<int,int,bool>' に変換することができません。
ids.Where(async x =>
{
var name = await GetNameAsync(x);
return name.StartsWith("Hoge");
});
おお、コンパイルエラー!無慈悲なんでなんで?というのも、asyncを使うと何をどうやってもTask<bool>しか返せなくて、つまりFunc<T,Task<bool>>となってしまい、Whereの求めるFunc<T,bool>に合致させることは、できま、せん。
Whereだけじゃありません。ラムダ式を求めるものは、みんな詰みます。また、Selectで一度Task<T>が流れると、以降のパイプラインは全てasyncが強いられ、結果として……
// asyncでSelect後はTask<T>になるので以降ラムダ式は全てasyncが強いられる
// これはコンパイル通ってしまいますがkeySelectorにTaskを渡していることになるので
// 実行時エラーで死にます
ids.Select(async id => new { Id = id, Name = await GetNameAsync(id) })
.OrderBy(async x => (await x).Id)
.ToArray();
Selectがパイプラインにならず、むしろ出口(ForEach)になっている。自由はない。
ではどうするか。ここは、一度、配列に詰めましょう。
// とある非同期メソッドのあるClassがあるとして
var models = Enumerable.Range(1, 10).Select(x => new ToaruClass());
// 以降の処理で使う非同期系のメソッドなり何かを、全てawaitで実体化して匿名型に詰める
var preload = await models
.Select(async model => new
{
model,
a = await model.GetAsyncA(),
b = await model.GetAsyncB(),
c = await model.GetAsyncC()
})
.WhenAll();
// そうして読み取ったもので処理して、(必要なら)最後に戻す
preload.Where(x => x.a == 100 && x.b == 20).Select(x => x.model);
概念的にはプリロード。というのが近いと思います。最初に非同期なデータを全て取得しまえば、扱えるし、ちゃんと並列でデータ取ってこれる。LINQの美徳である無限リストが取り扱えるような遅延実行の性質は消えてしまいますが、それはshoganai。それに、LINQにも完全な遅延実行と、非ストリーミングな遅延実行の二種類があります。非ストリーミングとは、例えばOrderBy。これは並び替えのために、実行された瞬間に全要素を一度蓄えます。例えばGroupBy。これもグルーピングのために、実行された瞬間に全要素を舐めます。非同期LINQもまた、それらと同種だと思えば、少しは納得いきませんか?現実的な妥協としては、このラインはアリだと私は思っています。分かりやすいしパフォーマンスもいい。
AsyncEnumerableの幻想、或いはRxとの邂逅
それでも妥協したくないならば、次へ行きましょう。まだ手はあります、良いかどうかは別としてね。注:ここから先は上級トピックなので適当に読み飛ばしていいです
そう、例えばWhereAsyncのようにして、Func<T,bool>じゃなくFunc<T,Task<bool>>を受け入れてくれるオーバーロードがあれば、いいんじゃない?って思ってみたり。こんな風な?
public static class AsyncEnumerable
{
// エラー:asyncとyield returnは併用できないよ
public static async IEnumerable<T> WhereAsync<T>(this IEnumerable<T> source, Func<T, Task<bool>> predicate)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
if (await predicate(e.Current))
{
yield return e.Current;
}
}
}
}
}
ただ、問題の本質はそんなことじゃあない。別にyield returnが使えなければ手書きで作ればいいわけで。そして作ってみれば、本質的な問題がどこにあるのか気づくことができます。
class WhereAsyncEnumerable<T> : IEnumerable<T>, IEnumerator<T>
{
IEnumerable<T> source;
Func<T, Task<bool>> predicate;
T current = default(T);
IEnumerator<T> enumerator;
public WhereAsyncEnumerable(IEnumerable<T> source, Func<T, Task<bool>> predicate)
{
this.source = source;
this.predicate = predicate;
}
public IEnumerator<T> GetEnumerator()
{
return this;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public T Current
{
get { return current; }
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public void Reset()
{
throw new NotSupportedException();
}
public void Dispose()
{
}
// ↑まではdoudemoii
// MoveNextが本題
public bool MoveNext()
{
if (enumerator == null) enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
// MoveNextはasyncじゃないのでawaitできないからコンパイルエラー
if (await predicate(enumerator.Current))
{
current = enumerator.Current;
return true;
}
}
return false;
}
}
MoveNextだけ見てもらえればいいのですが、predicateを使うのはMoveNextなわけです。ここがasyncじゃないと、AsyncなLINQは成立しません。さて、もしMoveNextがasyncだと?
public async Task<bool> MoveNext()
{
// ここで取得するenumeratorのMoveNextも
// 全て同一のインターフェイスであることが前提条件なのでTask<bool>とする
if (enumerator == null) enumerator = source.GetEnumerator();
while (await enumerator.MoveNext())
{
if (await predicate(enumerator.Current))
{
current = enumerator.Current;
return true;
}
}
return false;
}
これは機能します。MoveNextをasyncにするということは連鎖的に全てのMoveNextがasync。それが上から下まで統一されれば、このLINQは機能します。ただ、それってつまり、IEnumerator<T>を捨てるということ。MoveNextがasyncなのは、似て非なるものにすぎない。当然LINQっぽい何かもまた、全て、このasyncなMoveNextを前提にしたものが別途用意されなければならない。そして、それが、Ix-Async。
Ix-Asyncのインターフェイスは、上で出したasyncなMoveNextを持ちます。
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetEnumerator();
}
public interface IAsyncEnumerator<out T> : IDisposable
{
T Current { get; }
Task<bool> MoveNext(CancellationToken cancellationToken);
}
そして当然、各演算子はIAsyncEnumerableを求めます。
public static IAsyncEnumerable<TSource> Where<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate);
これの何が便利?IEnumerable<T>からIAsyncEnumerable<T>へはToAsyncEnumerableで変換できはするけれど……、求めているのはIEnumerable<Task<T>>の取り扱いであったりpredicateにTaskを投げ込めたりすることであり、何だかどうにもなく、これじゃない感が否めない。
そもそも、LINQ to Objectsから完全に逸脱した新しいものなら、既にあるじゃない?非同期をLINQで扱うなら、Reactive Extensionsが。
Reactive Extensionsと非同期LINQ
ではRxで扱ってみましょう。の前に、まず、predicateにTaskは投げ込めません。なのでその前処理でロードするのは変わりません。ただ、そのまま続けてLINQ的に処理可能なのが違うところです。
await ids.ToObservable()
.SelectMany(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
おお、LINQだ?勿論、Where以外にも何でもアリです。RxならLINQ to Objects以上の山のようなメソッドを繋げまわることが可能です。ところで、ここで出てきているのはSelectMany。LINQ to ObjectsでのSelectの役割を、Rxの場合はSelectManyが担っています。asyncにおいてForEachはSelectでRxでSelectはSelectMany……。混乱してきました?
なお、これの結果は順不同です。もしシーケンスの順序どおりにしたい場合はSelect + Concatを代わりに使います。
await ids.ToObservable()
.Select(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Concat()
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
ソーナンダー?ちなみにSelectManyはSelect + Mergeに等しい。
await ids.ToObservable()
.Select(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Merge()
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
この辺のことがしっくりくればRxマスター。つまり、やっぱRxムズカシイデスネ。とはいえ、見たとおり、Rx(2.0)からは、asyncとかなり統合されて、シームレスに取り扱うことが可能になっています。対立じゃなくて協調。自然に共存できます。ただし、単品でもわけわからないものが合わさって更なるカオス!強烈強力!
まとめ
後半のAsyncEnumerableだのIx-AsyncだのRxだのは、割とdoudemoii話です、覚えなくていいです。特にIx-Asyncはただの思考実験なだけで実用性ゼロなので本気でdoudemoiiです。Rxは便利なので覚えてくれてもいいのですが……。
大事なのは、async + Selectです。SelectはForEachなんだー、というのがティンとくれば、勝ったも同然。そして、プリロード的な使い方。そこさえ覚えれば非同期でシーケンス処理も大丈夫。
asyncって新しいので、今まで出来たことが意外と出来なくてはまったりします。でも、それも、どういう障壁があって、どう対処すればいいのか分かっていればなんてことはない話です。乗り越えた先には、間違いなく素晴らしい未来が待っているので、是非C# 5.0の非同期、使いこなしてください。