C#で別スレッドでループを回して処理を行う。

C# コンピュータ
C#

別スレッドを起動しその中でループを回し、ループ内で処理を行うプログラムがあります。基本的にサーバーなどのサービスのリクエストの受付などの処理を待つプログラムで使われるコードです。


bool _loopFlag = true;
public void Sample()
{
    var _ = Task.Run(async ()=>
    {
        while (_loopFlag)
        {
            try
            {
                // 処理を書く
            }
            catch (Exception e)
            {
                // 例外が発生した場合の処理
                Debug.Print("{0}", e.Message);
                throw;
            }
        }
     });
}

Sample()を呼び出すとTask.Run()でループが起動し、awaitしていないので直ぐに呼び出し元に戻ります。
基本的に_loopFlagがtrueの限りループし続けることになり、ループを終了する場合外部から_loopFlagをfalseにすることで停止します。
_loopFlagをboolではなくCancellationTokenを使ってループの終了フラグとし、外部から終了させる場合はCancel()メソッドを実行するような使い方が出来そうな感じがします。

using System.Diagnostics;
using System.Windows;

namespace TaskInLoop01;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    //bool _loopFlag = true;
    CancellationTokenSource? _cts = new();
    Task? _task = null;

    //public void Sample()
    public Task Sample(CancellationToken token)
    {
        return Task.Run(async ()=>
        {
            //while (_loopFlag)
            while(!token.IsCancellationRequested)
            {
                try
                {
                    // 処理を書く
                    Debug.Print("{0}", Environment.TickCount64);
                    await Task.Delay(1000);
                }
                catch (Exception e)
                {
                    // 例外が発生した場合の処理
                    Debug.Print("{0}", e.Message);
                    throw;
                }
            }
            Debug.Print("Loop End");
        });
    }
    public MainWindow()
    {
        InitializeComponent();

        Loaded += (s, e) =>
        {
            //Sample();
            _task = Sample(_cts.Token);
            Debug.Print("Loaded");
        };
        Closed += (s, e) =>
        {
            //_loopFlag = false;
            if (_task is not null)
            {
                _cts?.Cancel();
                _task.Wait();
            }
            Debug.Print("Closed");
        };
    }
}

WPFでウィンドウを表示するだけのプログラムですが、デバッグ実行では1秒ごとに数字が表示されるようになっています。
ウィンドウをクローズするとキャンセルとタスクの終了を待つようにしたつもりです。

Loaded
960719328
960720328
960721343
Loop End
Closed

ConcurrentQueueを使いループで処理するデータ(文字列)渡して1秒ごとに処理するコードを書いてみます。

using System.Diagnostics;
using System.Windows;
using System.Collections.Concurrent;

namespace TaskInLoop01;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    //bool _loopFlag = true;
    CancellationTokenSource? _cts = new();
    Task? _task = null;
    ConcurrentQueue<string> _request = new();

    //public void Sample()
    public Task Sample(CancellationToken token)
    {
        return Task.Run(async ()=>
        {
            //while (_loopFlag)
            while(!token.IsCancellationRequested)
            {
                try
                {
                    // 処理を書く
                    //Debug.Print("{0}", Environment.TickCount64);
                    if (_request.Count() > 0)
                    {
                        string? request = "";
                        _request.TryDequeue(out request);
                        if (request is not null)
                        {
                            Debug.Print("Request: {0}", request);
                        }
                    }
                    await Task.Delay(1000);
                }
                catch (Exception e)
                {
                    // 例外が発生した場合の処理
                    Debug.Print("{0}", e.Message);
                    throw;
                }
            }
            Debug.Print("Loop End");
        });
    }
    public MainWindow()
    {
        InitializeComponent();

        Loaded += (s, e) =>
        {
            //Sample();
            _task = Sample(_cts.Token);


            _request.Enqueue("A");
            _request.Enqueue("B");
            _request.Enqueue("C");
            _request.Enqueue("D");

            Debug.Print("Loaded");
        };
        Closed += (s, e) =>
        {
            //_loopFlag = false;
            if (_task is not null)
            {
                _cts?.Cancel();
                _task.Wait();
            }
            Debug.Print("Closed");
        };
    }
}

結果

Loaded
Request: A
Request: B
Request: C
Request: D
Loop End
Closed

A、B、C、Dと文字を渡しましたが、URLを渡してバックグラウンドでファイルをダウンロードするプログラムなどに使えそうな感じがします。

コメント