Karakuri.com

Fintechではたらくアプリケーションエンジニアの技術録

C#のTaskを使った非同期処理のタイムアウトの実装方法について

データ量に比例して処理時間が増えるコードを非同期処理で実行していたのですが、データ量が少ないとプログレスリングがすぐ消えて画面のチラつきになってしまうことがありました。このため、処理時間が一定以上の場合はプログレスリングを表示し、一定未満の場合は表示しないというコードを書く必要があります。今回はその処理を実装するためTaskのタイムアウトを使用しました。

プログレスリングを毎回表示する場合

まずは基本的な非同期処理の実装についてです。時間のかかる重い処理をTask.Runで実行することで別スレッドで実行します。Thread.Sleepが実行されている間はUIスレッドは空いているので、UIがフリーズすることはありません。

async void Method()
{
     ProgressRing.Visibility = Visibility.Visible;
     await Task.Run(() => Thread.Sleep(10000));
     ProgressRing.Visibility = Visibility.Hidden;
}

このケースはこれで問題ないのですが、Thread.Sleepの時間が動的に変わる場合はどうでしょうか?

async void Method(int time)
{
     ProgressRing.Visibility = Visibility.Visible;
     await Task.Run(() => Thread.Sleep(time));
     ProgressRing.Visibility = Visibility.Hidden;
}

多くの場合が100とか200とかだとプログレスリングのチラつきを指摘される可能性があります。このケースだとシンプルなのでtimeの値によってプログレスリングの表示を制御できますが、当然ながら実行してみなければ時間が分からないケースのほうが多いでしょう。

規定の時間が経過したら新たな処理を実行する

どうやればいいかと最初は考えましたが、Task.Waitを使えば結構簡単にできます。

async void Method(int time)
{
     var task = new Task(() => Thread.Sleep(time));

     task.Start();

     var isTimeOut = !await task.Wait(1000);
     if (isTimeOut)
     {
          ProgressRing.Visibility = Visibility.Visible;
          await task.Wait();
          ProgressRing.Visibility = Visibility.Hidden;
     }
}

Task.Waitにミリ秒を渡せば、その時間だけ待ってくれて、更に待っている間に終わったかどうかを返り値で教えてくれます。もし終わっていなければプログレスリングを表示するといったことができます。

非同期処理をタイムアウトで終了させる

同様の方法でTaskのタイムアウトのよる終了処理を実装することができます。

async void Method(int time, int timeOut)
{
     using (var tokenSource = new CancellationTokenSource()) 
     { 
          var task = new Task(() => Thread.Sleep(time), tokenSource.Token);

          task.Start();

          var isTimeOut = !await task.Wait(timeOut);
          if (isTimeOut)
          {
                tokenSource.Cancel();
          }
     }
}

キャンセルによる例外処理は別途行ってください。