0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C#勉] タイムアウト機能を考える

Posted at

はじめに

ここ半年ほどC#を使ってwindowsアプリを開発しています。
馴染みのない言語のため、日々勉強中です。

学習ログも兼ねつつ、
今後にも活かせるようにいくつかモジュール化できたらいいかもなーと考え、
検討の過程やコードを残せる範囲で残したいと思います。

今回のお題

ある処理の終了条件ですね。

クラスA から クラスB に処理を委譲、非同期で待ちます。
この時、一定時間が経過した場合 で処理を分けたい。
タイムアウト機能を実装したいという状況です。

以下のようなイメージ。

設計

  • Copilotに相談しつつ設計案と実装を進める
  • .NETの機能である TaskCompletionSource を使うのが良さそう
  • いくつかバリエーションがありますが、今回はクラスとして独立させておき、単一のクラス内で使いまわせるようにする
    • 異なるメソッドから、タイマーの開始と完了を呼び出せる
    • インスタンスを共有すれば、他クラスからも呼び出しok
    • キャンセル機能は、オプションでさらに改善できる模様

実装(サンプルコード)

こんなイメージ。
Testerがタイムアウトの開始やら完了やらする。

Program.cs
class Program
{
    static void Main(string[] args)
    {
        var tester = new Tester();
        tester.SuccessRun();
        tester.TimeoutRun();
        tester.TimerCheck();
    }
 }
Tester.cs
public class Tester
{
    private readonly TimeoutManager _timeoutManager = new TimeoutManager();

    public void SuccessRun()
    {
        // 3000msのタイムアウトを設定する
        string key = "task1";
        int timeoutMs = 3000;

        // 1000msで処理完了しタイマー解除する
        Task.Run(async () =>
        {
            await Task.Delay(1000);
            _timeoutManager.CompleteTimer(key);
        });

        bool completed = _timeoutManager.StartTimer(key, timeoutMs).GetAwaiter().GetResult();
        if (completed)
        {
            Console.WriteLine("正常に完了しました");
        }
        else
        {
            Console.WriteLine("タイムアウトしました");
        }
    }

    public void TimeoutRun()
    {
        // 3000msのタイムアウトを設定する
        string key = "task2";
        int timeoutMs = 3000;

        // 5000msで処理完了しタイマー解除する
        Task.Run(async () =>
        {
            await Task.Delay(5000);
            _timeoutManager.CompleteTimer(key);
        });

        bool completed = _timeoutManager.StartTimer(key, timeoutMs).GetAwaiter().GetResult();

        if (completed)
        {
            Console.WriteLine("正常に完了しました");
        }
        else
        {
            Console.WriteLine("タイムアウトしました");
        }
    }

    public void TimerCheck()
    {
        _timeoutManager.CompleteTimer("task3");
        bool completed1 = _timeoutManager.StartTimer("task4", 3000).GetAwaiter().GetResult();
        bool completed2 = _timeoutManager.StartTimer("task4", 3000).GetAwaiter().GetResult();
    }
}
TimeoutManager.cs
    /// <summary>
    /// タイムアウトを管理するクラス
    /// </summary>
    public class TimeoutManager
    {
        // マルチスレッドに対応した辞書
        private readonly ConcurrentDictionary<string, TaskCompletionSource<bool>> _timers = new ConcurrentDictionary<string, TaskCompletionSource<bool>>();

        public async Task<bool> StartTimer(string key, int timeoutMs)
        {
            // 重複チェックは仕様次第、今回は許容しない
            if (_timers.ContainsKey(key))
            {
                Console.WriteLine($"タイマー {key} は既に存在します");
                return false;
            }

            // オプションを指定しデッドロック防止する
            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            _timers[key] = tcs;

            // タイムアウト時間を計測するタスクを生成
            // 完了通知 と タイムアウト のどちらかが完了するまで待機
            var timeoutTask = Task.Delay(timeoutMs);
            var completedTask = await Task.WhenAny(tcs.Task, timeoutTask).ConfigureAwait(false);

            if (completedTask == tcs.Task)
            {
                _timers.TryRemove(key, out _);
                return true;
            }

            return false;
        }

        public void CompleteTimer(string key)
        {
            if (!_timers.TryRemove(key, out var tcs))
            {
                Console.WriteLine($"タイマー {key} はありません");
                return;
            }
            tcs.TrySetResult(true);
        }
    }
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?