MinimumAsyncBridge化してみてどうかと、 MinimumAsyncBridgeを作った感想など。
作業難易度
参考にした元があって、書き換えが必要だった部分もごく少量で、 大した手間もかけずに移植してます。最初から安定しているのも当たり前で。
まあほんとは、もっと昔から.NETがオープンソースだったら…というのはありますが。
IL2CPPで動かなくて困ったりした時期はありますが、この辺りは、別にTask
クラスのせいじゃなくてそこかしろでバグだらけだったので。
ただひたすらにIL2CPPの安定を待っていただけ。
導入した感想
やっぱり普通にC# 5.0のasync/awaitが書けるのはむちゃくちゃ楽です。 自分だけじゃなく、チーム全体で言っても、かなりの作業量が浮いてるんじゃないかと。
途中までIteratorTasksを使っていて、 思い立ったかのようにMinimumAsyncBridgeに移行するのに1・2週間ほど使いましたが、 その作業分は余裕で取り戻せているのではないかと思います。
何よりも、
- 最新のC#を問題なく使える
- 「もどき」じゃなく、標準
Task
の完全下位互換で、単一DLLで完全にコード共通化できる
という辺りは非常に大きい。
Taskクラスの問題
バックポーティング作業をやっていて感じたのは、Task
クラスの債務がでかすぎるという問題。
Task
クラスは、以下のような処理を全部担います。
- スレッド(
Task.Run
) - I/O完了ポート(非同期API)
- ハードウェアタイマー(
Task.Delay
) - 他とのつなぎ(
TaskCompletionSource
)、C++でいうfuture/promise
全部1つのクラス。他の言語だと、「他とのつなぎ」だけ用意して、 スレッドとかは別途そいつで軽く包んでるだけなことが多いんですけども。 .NETのTaskは割かし全部入り。
このうち、async/awaitを使うだけならTaskCompletionSource
だけあれば何とかなるんですよねぇ。
なのに、そこだけを抜き出して取ってこれない。
おまけでいろいろ、しかもスレッド関連とかいう難しい処理も持ってこないといけない。
例えば以下のような問題があったり。
-
GetAwaiter
が、.NET 4.5でTask
クラスに追加されてる- 型フォワーディングの仕組みでは、クラスの追加はバック ポーティングできるけど、既存クラスへのメソッド追加は無理
- 結果、.NET 3.5と4.5では動くけど、.NET 4では動かないライブラリになってる
-
非同期メソッドの戻り値が
Task
固定で、インターフェイスとかになってない- インターフェイスになっていれば、独自実装がしやすかったはず
Task
クラスみたいにでかいものを移植しなくて済む
全部1つでやってるので性能はいいし、利用者視点の利便性はいいとは思います。
そもそもなぜ非同期処理なんて面倒な事するかというとパフォーマンスを求めてなわけで、
Task
実装ではパフォーマンスを妥協できなかったというのはあるようです。
あと、async/awaitを変に汎用化しようとすると、
asyncメソッドのオーバーロード解決とかで苦労するらしい(非同期が絡むと型推論が効かなくなったり)。
利便性劣化に直結。
一方で、Task
を実装する側、
例えば.NET Coreで今絶賛クロスプラットフォーム化作業してる方々とか、
Monoの方々とか、
今回の僕みたいに独自にバック ポーティングすることにした人にとってはこのでかい実装はほんとに面倒。
C#のasync/awaitはそういうでっかい一枚岩の上に乗っかっちゃってるって意味では将来リスクは結構負っています。 この辺りはF#方面の人らは「だからC#はダメだ」とか言ったりするものの。 ここは方向性の違いというか、C#的には「性能と利用者利便性を考えたらTaskに乗るしかなかった」と言われると妥協せざるを得ず。
最近他の言語でもasync/await対応し始めてますけど、それらの言語でも「どこまで汎用化するか」は大きな争点になってるはずです。 まあ、async/awaitに関してはF#もC#も先駆者なので、負の遺産にはなりやすいかもなぁ(F#はパフォーマンス的にペナルティ結構負ってるはずだし、C#は一枚岩の上に載ってるリスクあり)という気はしなくもないです。 もっとも、それを心配しないといけなくなるのはまだまだ5年10年先の話だと思いますが。
まとめ
元コードがあるんであっさり動いて当然。 ただ、もっと最初からオープンだったらわざわざ自分が苦労する必要なかったという気もします。
C# 5.0登場時(アイディアが出たのは5・6年前。RTMが2012年)にも言われていたことではあるんですが、
Task
クラスはちょっと債務がでかすぎるよなぁというのは改めて思いました。
まあ、とりあえず安定して動いたし、その結果、かなり開発が楽になったので実装してよかったと思います。