この記事は「async/await
構文を使い始めの時期に私が勘違いしていたこと」を元に、「もしかして私も勘違いしているかもしれない…?」と自信がない方へ向けての「async/await
を間違えて使わない」ための記事です。C# / .NET を前提にしています。
Taskを極めろ!async/await完全攻略と併せて読んでいただくことで、より理解が深まると思います。
この記事では次のような方を対象としています。
-
async
をつけたメソッドは非同期で実行されると思っている -
async
をつけたメソッドはバックグラウンドスレッド(?)で実行されると思っている -
await
を書いた箇所は非同期(?)になると思っている(←非同期 is 何?って訊かれるとモニョる) -
この非同期メソッドには 'await' 演算子がないため、同期的に実行されます。'await' 演算子を使用して非ブロッキング API 呼び出しを待機するか、'await Task.Run(...)' を使用してバックグラウンドのスレッドに対して CPU 主体の処理を実行することを検討してください。
という警告が何を言ってるかよくわからない - なんかうまく非同期になっていないっぽい…?よし、とりあえず
Task.Run
を書いてみよう
これらのモヤモヤをひも解いていこうと思います。
async
をつけたメソッドは非同期とは限らない
メソッドに async
ってつけたことがありますか?
この記事を読んでいる方は、きっと1回くらいはつけたことがある方だと思います。
下記の2つメソッドを、それぞれ DoSomething();
DoSomethingAsync();
と呼び出したときの違いはなんでしょう?
public void DoSomething()
{
DoHeavyTask(); // ← 10秒かかる重い処理
}
public async Task DoSomethingAsync()
{
DoHeavyTask(); // ← 10秒かかる重い処理
}
「下のメソッドは非同期メソッドなのでバックグラウンドで実行される…?」と思いましたか?
残念!2つのメソッドは実質同じ(*1)です。
実質同じとは、つまり以下のように呼び出した場合、10秒かかるのは DoSomethingAsync
呼び出しである、ということです。
var stopwatch = Stopwatch.StartNew();
Console.WriteLine("before DoSomethingAsync: {0:N0}ms", stopwatch.ElapsedMilliseconds);
// ここではバックグラウンド?で DoSomethingAsync を開始だけしたつもり(非同期のつもり)
var task = DoSomethingAsync();
Console.WriteLine("after DoSomethingAsync: {0:N0}ms", stopwatch.ElapsedMilliseconds);
// ここで await することで完了を待っているつもり
await task;
Console.WriteLine("after await: {0:N0}ms", stopwatch.ElapsedMilliseconds);
before DoSomethingAsync: 0ms
after DoSomethingAsync: 10,002ms
after await: 10,003ms
2行目、予想に反したのではないでしょうか?
私にも2行目が 0ms
とか 1ms
とかになると思っていた時期がありました……
おいおい、async
って言ってるのに「非同期」じゃないじゃないか……
それもそのはずです。async
修飾子の意味はただ一つ。
await
を使うメソッドには async
をつけなければならないというルールだからつける(= async
をつけると await
キーワードが使えるようになる)。
これだけです。async
自体は await
を使うためのマーカーにすぎません。async
をつけてもつけなくても、それだけなら何も変わらない 1 のです。
冒頭に書かれていた この非同期メソッドには 'await' 演算子がないため、同期的に実行されます。~(中略)~を検討してください。
という警告。これは「await
が使われていない async
メソッドがあるとき」に出る警告です。
先の説明の通り、await
を使わない async
とつけたメソッドには**(async をつける)意味がない**ため、「何か勘違いしとらんか?非同期になっとらんやでそれ」という意味で警告が出るのです。
ここまでで「いやいや、戻り値の型が void
から Task
に変わっとるがな!一緒じゃなくね?」と思った方はいい気づき 2 です。
これについては後ほど触れるとして、async
をつけて使いたい await
というのはなんなのか?
次回はそこに焦点を当てたいと思います。
今回のまとめ
-
async
はawait
を使うためのマーカーにすぎず、それ自体は何もしてくれない - ただし、戻り値の型は
void
からTask
に変わるようだ(これについては後々説明があるようだ) -
async
はマーカーにすぎないのでawait
こそが大事らしい。await
って何?が次回のテーマのようだ