あうゑいと

C#で非同期メソッドを呼び出す際に使うawaitステートメント,この挙動を誤解していました.非同期処理を同期的な順で処理しつつ元スレッドを専有しない便利なやつ,という認識でしたが,ちょっと違うぽ.私が誤解していたawaitの挙動は以下のとおりでした.

  1. 別スレッドでタスクを開始する.
  2. 元スレッドは一旦解放される.(だから元スレッドでの他のタスクが動かせる)
  3. 別スレッドでの処理が終わったら,元スレッドでawaitの続きを処理する.

正しいawaitの挙動は以下のとおりだったようです.

  1. 別スレッドでタスクを開始する.
  2. 元スレッドは一旦そこでメソッドを抜ける.(だから呼出元の続きの処理が並行で消化される)
  3. 別スレッドでの処理が終わったら,元スレッドでメソッド内のawaitの続きを処理する.

大抵,非同期メソッドをawaitで呼ぶ非同期メソッドは,それ自体もawaitで呼ばれます.この場合の挙動は正解と誤解に差がないため,今まで気づけないでいました.

自戒を込め,気合い入れて例示してみます.まず,普通の例.ま,こうなるよね.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task _ = MethodA();
        }

        static async Task MethodA()
        {
            Console.WriteLine("MethodA started.");
            await MethodB();
            Console.WriteLine("MethodA finished.");
        }

        static async Task MethodB()
        {
            Console.WriteLine("MethodB started.");
            await Task.Run(() =>
            {
                Console.WriteLine("Run started.");
                Thread.Sleep(3000); // HeavyTask
                Console.WriteLine("Run finished.");
            });
            Console.WriteLine("MethodB finished.");
        }
    }
}
実行結果:
MethodA started.
MethodB started.
Run started.
(ここで3秒待ち)
Run finished.
MethodB finished.
MethodA finished.

そして,これが私の理解と違った例.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task _ = MethodA();
        }

        static async Task MethodA()
        {
            Console.WriteLine("MethodA started.");
            Task _ = MethodB(); // <-------- ここを変えた
            Console.WriteLine("MethodA finished.");
        }

        static async Task MethodB()
        {
            Console.WriteLine("MethodB started.");
            await Task.Run(() =>
            {
                Console.WriteLine("Run started.");
                Thread.Sleep(3000); // HeavyTask
                Console.WriteLine("Run finished.");
            });
            Console.WriteLine("MethodB finished.");
        }
    }
}
実行結果:
MethodA started.
MethodB started.
MethodA finished.
Run started.
(ここで3秒待ち)
Run finished.
MethodB finished.

誤解していた私は,「普通の例」との違いが「MethodBの処理中に元スレッドで他のタスクが動かせるかどうか」だけで,ゆえに同じ実行結果になると思っていました.つまり,思っていた動きは以下のとおりです.

  1. MethodA started.
  2. 元スレッドでMethodB started.
  3. 別スレッドで3秒かかる処理.元スレッドは別スレッドの処理完了待ち.
  4. 3秒後,元スレッドでMethodB finished.
  5. 呼出元のMethodA finished.

で,正しい動きはなるほど,以下のとおりとなるわけです.

  1. MethodA started.
  2. 元スレッドでMethodB started.
  3. 別スレッドで3秒かかる処理開始.元スレッドはMethodBを抜けて(中断して)呼出元の続きのMedhodA finished.
  4. 3秒後,元スレッドでMethodB finished.

今日観たアニメ(4081)

  • [B]七つの大罪 神々の逆鱗「団長へ」(第15話 1/22夜)

    坊ちゃま大好き上級悪魔,倒したと思ったら生きてて,倒したと思ったら生きてて,怖い.一方の大罪はボロボロだし,ゴウセルは終了だし,さすがにそろそろ団長に目覚めていただかないと.そーいや良い年頃の大罪はどうしてん.

  • [B]あひるの空「最高の始まりと最悪の始まり」(第16話 1/22夜)

    福山声に次いで小西声もみんな大好き奈緒ちゃん.とりあえず試合に集中せよと.一人でマンツーマンディフェンスに勤しむ空,体力的にこれはしんどいっしょ.マジメヤンキーのレイアップ決まってヨカタ.

  • [C]ラディアン「消えゆく命の灯火~Tragedy」(第2期16話 1/22夜)

    黒幕という汚名を被せられていた女王,隊員を思って敵陣へ突っ込むの迫力あるわ.そーいや魔法使いは皆何かしらの呪いを受けているものなんでしたっけ.だとしたら女王は巨大化の呪いなんでしょうか.隊長の苛烈な剣戟と対する千葉繁声,暑苦しい.

  • [C]魔術師オーフェンはぐれ旅「我が呼び声に応えよ獣」(第3話 1/22深夜)

    結果的にはオーフェンが先陣を切り,チームで竜を倒すという先生の宣言どおり.殲滅隊側の牙の塔の旧友も竜化した旧友を喪いました.というオチかと思いきや,まさか先生と竜の中身が入れ替わっていたとは.