async/await不要論

43
async/await不要論 2013/3/23 bleis-tift

Upload: bleis-tift

Post on 29-Nov-2014

19.465 views

Category:

Technology


2 download

DESCRIPTION

並列/並行基礎勉強会での発表資料です。 補足エントリもどうぞ。 http://bleis-tift.hatenablog.com/entry/asyncawaitdis

TRANSCRIPT

Page 1: async/await不要論

async/await不要論2013/3/23

bleis-tift

Page 2: async/await不要論

自己紹介いつの間にか発表者にされていた系

仕事ではきょんくんをいじって遊んでます

なんか発表者にされていたので、仕事中にきょんくんをいじっていたらきょんくんがロックフリーの発表をすることになりました

基礎の話とかできないので、基礎勉強会なのに応用よりの話をします

Microsoft MVP for Visual F# を受賞してますが、今日はC#の話が中心です

Page 3: async/await不要論

async/await不要論・・・の前に

Page 4: async/await不要論

昔話をします

初めて買ったPCのCPUは Duron 850MHz (2000年ごろ)

ハイエンドCPUが、ちょうど1GHzを越えたあたり

◦ PentiumⅢ1GHzとか、Athlon 1.2HGzとか

このころは、「2010年には20GHzのCPUを実現」とか言ってた

◦ 同じように、Hyper-Threadingやマルチコアの「サーバ用途での」重要性も言われ始めた

Intelはこの頃デスクトップ向けCPUでクロック周波数を向上させ続けていた

◦ プログラマにとっては、「フリーランチの時代」

しかし、2003年にクロック周波数の向上ペースは落ちてしまった

◦ 増え続ける発熱に対処できなくなった

◦ 2013年3月現在、x86向けCPUでの最高クロック周波数は4.2GHz

◦ フリーランチ時代の終焉・・・?

Page 5: async/await不要論

CPUはデュアルコアへ2005年に、Pentium4のダイを2つ搭載したPentium Dや、Athlon64のデュアルコア版であるAthlon64 X2が登場

この頃から、「時代はマルチコア、メニーコアだ」と言われ始める

◦ 「2015年にはコアの数は100以上になる」

しかし、未だにデスクトップ向けCPUのコア数は最大8

◦ メーカー製PCはいまだに4コアどころか、2コアのものも珍しくはない

◦ 「CPUメーカーはコア数を増やす方向にシフトした」のような記述は、半分正解で半分間違っているので、そういう記事・書籍は眉に唾をつけて読むこと

クロック周波数も頭打ち、メニーコアも当分実現しそうにない・・・CPUメーカーの怠慢?

Page 6: async/await不要論

いやいやクロック周波数だけが性能を表すわけではない

◦ クロック周波数では測れない部分で、シングルスレッド性能は伸び続けている

◦ フリーランチはそれなりに継続中

一般的なPCの用途にメニーコアが必要とされていない

◦ 現状、最重要なのはいまだにシングルスレッド性能

◦ コアを増やしてもシングルスレッド性能は向上しない

Page 7: async/await不要論

アムダールの法則

並列化できない処理並列化できる処理

15%

50%

並列化できない処理

並列化できる処理

50%

60%

Page 8: async/await不要論

現状の把握

並列化できない処理が支配的な現状、マルチコアCPU・メニーコアCPUは作ったとしても買ってもらえない

◦ デスクトップPCではマルチコアCPU・メニーコアCPUへの移行は当分先(移行しない可能性も)

これからは並列プログラミングが重要になる!という風潮について

◦ サーバサイドではそれこそ10年以上前から重要だった

◦ クライアントサイドではキラーアプリが作れていない現状、それほど重要ではないのでは?

◦ キラーアプリ作るのが使命なんだ!って人→応援したい

◦ 単純に楽しい!って人→すばらしい思う

◦ 将来のためにやっておこう、って人→いいと思う

◦ なんかそういう風潮だから・・・って人→いったん、立ち止まってみてもよいのでは?

「とはいっても、C#5.0から言語仕様にasync/awaitが追加されてますよ?」

◦ よし、ではそのあたりから話をしよう

Page 9: async/await不要論

今日話すこと並列プログラミングと非同期プログラミング

async/await以前の非同期プログラミング

async/awaitの登場

async/await不要論

async/await必要論

そしてF#へ

Page 10: async/await不要論

並列プログラミングと非同期プログラミング

Page 11: async/await不要論

マルチスレッド化の目的マルチスレッド化する目的は、大きく分けて2つある

1. 複数のコアを活用し、処理を高速化する(並列プログラミング)

2. 処理をブロックさせないように、バックグラウンドで重い処理を走らせる(非同期プログラミング)

前者は複数のコアがなければ意味がないが、後者は単一コアであっても意味がある

◦ ○ CPUがマルチコア・メニーコアに向かうから並列プログラミングは重要である

◦ × CPUがマルチコア・メニーコアに向かうから非同期プログラミングは重要である

◦ 非同期プログラミングの重要性を説くためにマルチコア化の流れを持ち出すのはおかしい

今日は、並列プログラミングではなく、非同期プログラミングの話をします

Page 12: async/await不要論

非同期プログラミング非同期プログラミングにも、大きく分けて2つの目的がある

1. 重い処理をバックグラウンドで走らせる

2. 非同期I/OによってI/Oの待ち時間を隠す

今日はこれらを実装する側というよりも、使う側視点

◦ なのでどっちも同じようなものとして扱います

非同期コードを束ねたものを複数コアCPUで実行すると、並列動作する

◦ これを並列プログラミングと言っていいのかは・・・わかりません><

Page 13: async/await不要論

async/await以前の非同期プログラミング

Page 14: async/await不要論

.NET Frameworkにおける非同期プログラミングの移り変わり

Begin/EndによるAPM

(Asynchronous Programming Model)

完了イベントによるEAP

(Event-based Asynchronous Pattern)

TaskによるTAP

(Task-based Asynchronous Pattern)

TAP + async/await構文

.NET Framework 1.1

2.0

4.0

4.5

Page 15: async/await不要論

各手法の比較のためのお題

2つのWebサービスから情報を取ってきて表示する

◦ ただし、WebサービスBから情報を取ってくるには、WebサービスAから取得した情報が必要

これを同期プログラミングで書くとこんな感じ

でもこれだと、PrintSomeData の処理が終わるまで、呼び出し元をブロックしてしまう

GetA や GetB を、AMP/EAP/TAPそれぞれで非同期にした場合にPrintSomeData がどうなるかを見てみましょう

Page 16: async/await不要論

APM(非同期プログラミングモデル)

BeginHoge メソッドに完了後の処理的なものを渡すスタイル

コールバック地獄

Begin に渡すのは完了後の処理ではない。対応する End を呼び出して完了する

コールバックの引数を、正しく対応する End に渡す必要がある

Begin に対応する End を呼ばないと、リソースリークの原因になる

これでもだいぶマシになった方で、.NET Framework1.1時代はラムダ式も無名デリゲートもなかったので、これよりももっと地獄だった

Page 17: async/await不要論

EAP(イベントベースの非同期プログラミングのデザインパターン)

完了イベントに完了後の処理を登録するスタイル

やっぱりコールバック地獄

HogeAsync を呼び出す前に完了後の処理を登録する必要があるため、処理の記述の順番と実際の処理の順番が逆転している

えっ、これAMPの方がマシじゃね・・・?

ここには出てこないけど、GetAAsyncとかの実装側はそれはもう大変なコードが必要だった

Page 18: async/await不要論

TAP(タスクベースの非同期パターン)

メソッドに完了後の処理を渡すスタイル

結局コールバック地獄

でも一番すっきりしててわかりやすい

GetAAsyncの実装はタスクをStartNewするだけなので、実装する側も使う側も労力が一番少ない

(完了後の処理をGetAAsyncの引数としてではなく、Taskクラスのメソッドに渡すようにしたのがGood)

Page 19: async/await不要論

各手法の比較どれもコールバック地獄

APMは、ジェネリクスがなかった時代に戻り値の型を厳密に指定するためにああいうAPIになった。許す。

TAPは、ジェネリクスがある時代に作られた、APMの正当進化系。良い。

EAPは・・・どうしてそうなった。

Page 20: async/await不要論

同期版とTAP版を比べてみる

同期版を機械的に変換して、非同期版が作れそう・・・!

Page 21: async/await不要論

async/awaitの登場

Page 22: async/await不要論

async/awaitAPMにしてもEAPにしてもTAPにしても、コールバックの嵐だった

コールバックによるプログラミングはネストも深くなり、見通しが悪い

C#5.0から実装された async/await を使うと、同期版のように書かれたコードをコンパイラが非同期版に変換してくれる

コールバック地獄からの解放

◦ 「垂直落下的なコード」て表現されてることもあるとおり、読みにくくない

◦ 逆に、これでどこがどう非同期で動くのかイメージしにくいかも?

Page 23: async/await不要論

同期版とTAP+async/await版を比べてみる

ほとんど同じ

◦ メソッド名の語尾がAsyncに(そういう慣例・規約)

◦ メソッドの先頭に async がついている

◦ GetAAsync/GetBAsync の呼び出しの前に await がついている

async/await、素晴らしいじゃないか・・・

Page 24: async/await不要論

async/await不要論

Page 25: async/await不要論

async/await再考(not 最高)

async/await は確かに便利だけど、結局やっていることは構文の変換◦ ラムダ式のネストをフラットにしているだけ(実際は違うんだけど)

構文の変換といえば、C#にはクエリ式があるじゃない!

クエリ式で書けそうじゃないですか?

Page 26: async/await不要論

クエリ式

その名の通り、何らかのデータに対する「問い合わせ(クエリ)」を言語組み込みの構文として用意したもの

その名に反して、規定されたシグネチャに適合すればデータに対する問い合わせである必要はない

◦ 以下の2つの関数があれば M<T> 型の式をクエリ式に組み込める

◦ M<T> 型の m と、Func<T, U> 型の f に対して、m.Select(f) の形で呼び出せる関数

◦ M<T> 型の m と、Func<T, M<U>> 型の f と、Func<T, U, V> 型の g に対して、m.SelectMany(f, g) の形で呼び出せる関数

Page 27: async/await不要論

拡張メソッドで2つの関数を実装

Task<T> にインスタンスメソッドを後付けすることはできないので、拡張メソッドで

効率とか異常系とか細かいことは、今回はパスで

Unwrap が Task<Task<T>> を Task<T> にしてくれる

ContinueWith は F<Task<T>, U> を受け取ってTask<U> を返すので、U が Task<V> だと、

Task<Task<V>> になってしまう

Page 28: async/await不要論

SelectManyは元のコードの構造を写し取ったような構造をしている

Page 29: async/await不要論

クエリ式で書いてみる

async/await と同じことができた!

async/await 版との違いは◦ 通常のC#の文法ではなく、クエリ構文を使う

◦ 最後に全体を変数に格納する必要があるConsole.WriteLine が直接使えない(let で受けるか、上のように非同期化する)

◦ async が不要

◦ 何か特殊なことをやっていると分かりやすい

Page 30: async/await不要論

さらにAPMをクエリ式で直接扱うようなSelect/SelectManyを実装できる

◦ APMをTAPに変換するのではなく、APMをAPMとしてクエリ式で扱える

EAPもおそらくクエリ式で直接扱える・・・んじゃないかなぁ

◦ できなくてもTAPに変換すればTAPとしてクエリ式で扱える

すべての非同期モデルを、クエリ式という統一した構文で扱える!

async/awaitなんていらんかったんや・・・

Page 31: async/await不要論

async/await必要論

Page 32: async/await不要論

クエリ構文 VS async/await構文クエリ構文 async/await構文

メソッドの制約 なし asyncをつけ、戻り値も制限

式全体の型 Task<T> T

通常の構文との差 大 小

対応している制御構文 逐次と、条件演算子による分岐 逐次、分岐、繰り返し

async/await は大体の制御構文と混ぜて記述できる(しかも型がT)のが大きい

クエリ構文でこれは厳しい・・・

Page 33: async/await不要論

やっぱりasync/awaitだよね!だがしかし。

catchで使えない!

finallyで使えない!

Page 34: async/await不要論

C#さんは

「こういう便利な機能追加したんですよ!」

「へぇ、すごい!」

「でもここでは使えないんですけどね!」

っての多すぎやしませんか!!!

readonly で読み取り専用にできるけど、フィールドでしか使えない

var で型を省略できるけど、ローカル変数でしか使えない

yield で簡単に IEnumerable<T> 作れるけど、ラムダ式の中では使えない

async/await で簡単に非同期処理書けるけど、catch の中では使えない←New!

Page 35: async/await不要論

個人的にはですねクエリ式にもっと頑張ってもらいたかった

◦ 名前からして、クエリ専用なことが明らか

◦ だけど仕組みはものすごい汎用性がある

◦ SQLみたいな感じだよー!とするにはつらい(JOINとかな!)

無理なものは仕方がない

◦ そこでF#ですよ!

Page 36: async/await不要論

そしてF#へ

Page 37: async/await不要論

F#のコンピュテーション式F#には、C#のクエリ式のように「構文を変換する仕組み」として、コンピュテーション式というものがある

「コンピュテーション(計算)」とあるように、とても汎用的な仕組み

F#標準で、非同期ワークフローが扱える

Page 38: async/await不要論

コンピュテーション式とクエリ式

コンピュテーション式 クエリ式

目的 計算一般 クエリ

構文 他の構文に近い 他の構文とは全く別

制御構文 順次、分岐、反復などに対応可能 順次と(制限のある)分岐のみ

他にも、F#は同名の変数により変数をシャドーイング(隠蔽)したり、アンダースコアによって値を捨てたりできる

.NET Framework2.0移行であればF#の基本機能はすべて使える

Page 39: async/await不要論

例外処理中の非同期処理だってC#は出来なかったけど、F#ならできちゃう

Page 40: async/await不要論

それぞれの使い分けF#が使えるのであれば、F#を使う(.NET Frameworkのバージョンは問わない)

そうではなく、C#5.0/.NET4.0以降が使えるのであれば、async/awaitを使う

そうではなく、Taskが使えるのであれば、TAP + クエリ式を使う(RXでも可?)

そうではなく、C#3.0が使えるのであれば、APM + クエリ式を使う

そうではなく、C#2.0が使えるのであれば、APMを無名デリゲートとともに使う

C#1.2しか使えないのであれば、APMをメソッドとともに使うしか選択肢はない

EAPは基本、使わない

Page 41: async/await不要論

さいごに

Page 42: async/await不要論

async/awaitの潜在能力?async/await は調査が全然足りてない

もっと面白いことができる可能性もある

◦ async/awaitも、クエリ式同様「こういうシグネチャを持っていればいい」系

◦ クエリ式をクエリ以外に使えるように、async/awaitも非同期処理以外に使えるかも・・・?

そんなことしてると「ふつうのC#プログラム」からどんどん離れていきますけどね

Page 43: async/await不要論

最後にこれだけは言いたい

クエリ式はモナド用の構文!異論は認めない!!!