あうゑいと

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.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です