C#で関数をタイムアウトさせる方法

サンプルコード

なにげなく関数を走らせたら予想外の時間がかかり、タイムアウトの仕組を入れたいと思うことは、プログラマあるあるだと思います。

つい、この間、簡単な方法を見つけました。

  1. 関数をTask.Runで走らせる
  2. Task.Delayで、タイムアウト用のタイマを走らせる
  3. 2つのTaskのうち、早く終わったTaskTask.WaitAnyで検出して次の処理に移る

コンソールアプリケーションのサンプルを以下に示します。

// タイムアウトさせたい時間の入力
Console.Write("タイムアウト時間[ms]を入力してください: ");
var timeout = Console.ReadLine() ?? throw new NullReferenceException();

// 時間のかかる処理をタスクとして実行する(ここでは1秒を要する)
var tasksToBePerformed = Task.Run(() =>
{
    const int REQUIRED_TIME = 1000; // 1秒
    Task.Delay(REQUIRED_TIME).Wait();
    return "関数終了";
});

// 処理とタイムアウトのどちらが先に終了するか待機
var index = Task.WaitAny([tasksToBePerformed, Task.Delay(int.Parse(timeout))]);
// 先に終了した結果を表示
Console.WriteLine(index switch
{
    0 => tasksToBePerformed.Result,
    1 => "タイムアウト",
    _ => throw new InvalidOperationException("Unexpected task index.")
});

初めの3行は、コマンドラインでタイムアウト時間を入力するための行です。

constからreturn迄の3行で関数を表しています。
1秒経ったら”関数終了”という文字列を返します。
returnを書かなければvoidをシミュレートします。
勿論、ここに本当の関数を書いていただいても構いません。
関数は、Task.Runで囲んでtasksToBePerformedという名前のタスクとします。

タイムアウト用のタイマは、Task.Delay(int.Parse(timeout))で表現しています。timeoutはコマンドラインで入力するタイムアウト時間です。

これら2つのTaskのうち、早く終了する方のindexTask.WaitAnyで検出します。
本サンプルは、このindexに基づいて、どちらのTaskが先に終わったかをConsoleに示します。

動作確認

先ず、タイムアウト時間として、500 msを入力してみます。
関数は、1 s(1000 ms)ですので、タイムアウトとなります。

タイムアウト時間として1500 msを入力すると、関数が先に終わるので、「関数終了」と表示されます。

まとめ

なじみの薄い、”Task“を使う必要があります。でも、AwaitAsyncといった、もっと理解しにくい非同期のおまじないを使う必要はありません。よって、自分には分かり易いです。

コメント

  1. abc より:

    この処理は待つのをやめるだけで、処理自体は裏で動き続ける設計ですね

    • みすく より:

      abcさん、コメントをありがとうございます。
      おっしゃられる通り、処理自体は裏で動き続けています。

タイトルとURLをコピーしました