LoginSignup
60
42

More than 5 years have passed since last update.

async/await 構文で私が勘違いしていたこと~第1話~

Last updated at Posted at 2018-06-01

この記事は「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 というのはなんなのか?
次回はそこに焦点を当てたいと思います。

今回のまとめ

  • asyncawait を使うためのマーカーにすぎず、それ自体は何もしてくれない
  • ただし、戻り値の型は void から Task に変わるようだ(これについては後々説明があるようだ)
  • async はマーカーにすぎないので await こそが大事らしい。await って何?が次回のテーマのようだ

  1. 実際には async でマークされたメソッドはビルド時に特殊な形に展開されます。また、その関係で例外発生時の挙動が変わります。ただし「非同期になるか?同期のままか?」の観点で言えば async をつけただけでは何も変わらないと考えて OK です 

  2. この変化が「実際には async でマークされたメソッドはビルド時に特殊な形に展開されます。また、その関係で例外発生時の挙動が変わります。」と関連します。 

60
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
42