読者です 読者をやめる 読者になる 読者になる

ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

async/awaitキーワードのつかいどころを調べてみた

使いまくってもハマるだけであまりいいことはないみたいなので、つかいどころだけ。
オーバーヘッドも馬鹿になりませんからね。

非同期処理はスレッドベースからタスクベースにしよう

そもそもの前提ですが、スレッドベースの非同期処理は今や非推奨らしいです。その証拠に、ストアアプリ版のAPIからはThreadクラスが削除されています。なぜかというと、スレッドを作ったり切り替えたりするコストが大きいからだそうです。スレッドの切り替え処理にも結構オーバーヘッドがかかりますし、だいだいひとつのスレッドを作るのにローカルスタックを1MB消費するとか。1000スレッドくらい作るとメモリを食べつくして飢え死にします。やってられませんね。
非同期処理をするときは、Threadクラスを直接使うのではなくTask、せめてThreadPoolやTimerを使うようにしましょう。こうすることで、既存のスレッドをなるべく使いまわすように裏でよきに計らってくれるので、作らられるスレッドを必要最小限に抑えることができます。ざっくりとした作戦はそれほど難しくなくて、降ってきたタスクをキューに入れて、空いているスレッドに実行させるという感じです。

外の世界とやり取りする処理は非同期にしよう

外の世界とのやり取りが絡む処理は非同期にしましょう。たとえばファイル入出力やデータの送信などです。ご存知の方も多いと思いますが、ストアアプリ向けに提供されているAPIは同期的に入出力するメソッドがことごとくなくなっています。こういうときのためのasync/awaitです。

50msec以上かかる(かもしれない)処理は非同期にしよう

処理時間がかかりすぎている目安として50msecというのが提示されています。これを超える(かもしれない)処理は非同期処理以外提供しないというのがいまどきの.NETにおける潮流らしいです。これまで紹介してきた削除されたAPIはこの考えに基づいているそうです。

awaitするかContinueWithするか

Taskクラス、またはTaskクラスを返すメソッドをawaitすると実行されるスレッドが呼び出し元のスレッドに戻ります。これはこれでいいのですが、入出力を受け取った後重たい処理をするようなときは、いったん元のスレッドに戻るのは若干ながら負担になります。TaskクラスとTaskクラスのインスタンスはContinueWithとContinueWithというメソッドを使うと、スレッドを呼び出し元に戻さずに非同期処理ができます。

var read = await ReadAsync();
//ここでスレッドが呼び出し元に戻る
var result = await Task.Run(() => HeavyTask(read));

//ContinueWithを使うと
var result = await ReadAsync().
//ここでスレッドは呼び出し元に戻らない
                   ContinueWith(r => HeavyTask(r));

まとめ

スレッドベースの非同期処理は書かない
入出力は非同期処理するように書く
時間がかかる処理は非同期処理するように書く
ContinueWithを使うところは使う