2019年03月17日
川俣晶の縁側ソフトウェア技術雑記 total 3272 count

非同期メソッドではないにも関わらず、CancellationTokenSource.Cancelメソッドは同期的に実行されない

Written By: 川俣 晶連絡先

問題 §

 以下のプログラムでXYZ0,XYZ1,XYZ2,XYZ3の順番を期待したのに、実際はXYZ0,XYZ2,XYZ3,XYZ1である。

using System;

using System.Threading.Tasks;

namespace CancelToken1

{

    class Program

    {

        static async Task Main(string[] args)

        {

            try

            {

                var source = new System.Threading.CancellationTokenSource();

                _ = Task.Delay(1000).ContinueWith((task) =>

                 {

                     Console.WriteLine("XYZ0");

                     source.Cancel();

                     Console.WriteLine("XYZ1");

                 });

                await Task.Delay(-1, source.Token);

            }

            catch (System.Threading.Tasks.TaskCanceledException)

            {

                Console.WriteLine("XYZ2");

            }

            Console.WriteLine("XYZ3");

        }

    }

}

原因 §

 source.Cancel();は非同期メソッドではないにも関わらず非同期的に実行される。つまり、Cancelメソッドは即座に待機中のメソッド(Delay)で例外(TaskCanceledException)を発生させるため、待機中のDelayメソッドの実行が即座に再開されてしまう。コンテキストスイッチのタイミングがまわってくるまで、Cancelメソッドの次のメソッド(Console.WriteLine("XYZ1");)は実行されない。

解決 §

 待機中のメソッドが再開する前に実行すべきコードはCancelメソッドの呼び出しよりも手前に必ず置く。

 たとえば以下のように書き換える。

                     Console.WriteLine("XYZ0");

                     Console.WriteLine("XYZ1"); // 移動された行

                     source.Cancel();