1726171734
2024-09-10 10:08:08
コード内に、決して終了してはいけないと思われるループを書いたことはありますか?これは、バックグラウンドで実行されるサービスなど、継続的に実行するように設計されたソフトウェアでは一般的なシナリオです。たとえば、Web サービスは通常、特定のアドレスとポートをリッスンし、受信接続がタスクを完了するまで無期限に待機します。
これらの接続はいつでも到着する可能性があるため、サービスは常に準備ができている必要があり、多くの場合、無限ループが発生します。
C# の基本的な例を次に示します。
while (true)
{
if (Listener.Pending())
{
ISocketWrapper client = new SocketWrapper(Listener.AcceptSocket());
await AcceptConnectionsAsync(client);
}
}
このループでは、サービスは新しい接続が保留中かどうかを確認します。そうであれば、接続を受け入れてクライアントを処理します。それ以外の場合は、ループが継続し、同じチェックが何度も繰り返されます。
一見すると、無限ループは合理的な解決策のように思えます。結局のところ、サービスは常に実行されている必要があります。サービスを停止する必要がある場合は、プロセスを終了するだけです。これは常に問題になるとは限りませんが、最もエレガントな解決策でも、最も安全な解決策でもありません。
無限ループの問題
無限ループは現時点では許容できるように思えますが、サービスを拡張する必要がある場合はどうなるでしょうか?たとえば、リスニング アドレスまたはポートを更新する必要がある場合はどうすればよいでしょうか?サービス全体を再起動すると機能する可能性がありますが、これは多くの場合不便であり、進行中の他の操作が中断される可能性があります。さらに悪いことに、サービスを強制的に停止すると、特にサービスが顧客のリクエストを処理中の場合には、データの損失やその他の予期せぬ結果が生じる可能性があります。
ここで、より良い解決策が登場します。プロセスを突然中断することなく、どこからでも呼び出すことができ、ループからエレガントに抜け出す方法が必要です。ここは、 キャンセルトークン。
キャンセルトークンとは何ですか?
そして キャンセルトークン は、タスクの停止要求を通知できるようにする C# のオブジェクトです。これをコードで使用すると、無限ループの制御された終了条件を実装でき、シャットダウン要求が発生したときにサービスが現在の作業を正常に完了できるようになります。
を統合して前の例を改善しましょう。 CancellationToken :
public async Task StartListeningAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (Listener.Pending())
{
ISocketWrapper client = new SocketWrapper(Listener.AcceptSocket());
await AcceptConnectionsAsync(client);
}
}
// Cleaning work
}
今回は、無限にループするのではなく、ループが続く限り継続します。 CancellationToken キャンセル要求を通知しません。このトークンを使用すると、サービスを停止する必要がある場合に、サービスを制御して閉じることができます。
キャンセル トークンは 1 つで十分ですが、2 つあるとさらに良いです…
実際、複雑なアプリケーションには、複数のタスクが相互にリンクされている場合もあれば、リンクされていない場合もあります。親タスクをキャンセルする必要がある場合は、子タスクもキャンセルできる必要があります。そうしないと、子タスクが完了するまで待機することになり、時間がかかる可能性があります。
これにはいくつかの戦略が存在します。最も簡単な方法は、 キャンセルトークン さまざまなサブタスクに移動します。たとえば、次の署名を少し変更すると、 AcceptConnectionsAsync 十分でしょう:
await AcceptConnectionsAsync(client, token);
すべての子タスクが常に親タスクとともにキャンセルされる場合は、このパターンで十分です。ただし、親タスクをキャンセルせずに子タスクをキャンセルすることはできません。
トークンのリストまたはグラフ
この問題を回避する良い方法は、 キャンセルトークン タスクを停止する際のより詳細な制御と粒度を得るために、タスクまたはモジュールごとに一意です。各トークンは、ニーズに応じてリストやグラフなどのデータ構造に保存されます。モジュールとそのトークンのキャンセルは、関連するサブタスクに伝播する可能性があります。
この動作を実装するには、いくつかの簡単な方法があります。たとえば、キーがタスクまたはモジュールの名前であり、要素が関連付けられたトークンである辞書を使用できます。これは、タスクを組織的に管理およびキャンセルするのに役立ちます。 C# での実装例を次に示します。
public class WebServer
{
public Dictionarystring, CancellationTokenSource> CancellationTokens = new Dictionarystring, CancellationTokenSource>();
public readonly CancellationTokenSource GlobalTokenSource = new CancellationTokenSource();
public async Task StopAsync(bool isRestart = false)
{
foreach (var tokenName in CancellationTokens.Keys)
{
Console.WriteLine($"Cancelling token {tokenName}: {CancellationTokens[tokenName].Token}");
CancellationTokens[tokenName].Cancel();
}
// Wait if needed for tasks to finish
await Task.Delay(1000); // Simulate wait
}
}
この例では、クラスは WebServer 辞書を使用して保存します キャンセルトークン さまざまなタスクまたはモジュールに関連付けられています。方法 StopAsync 辞書キーをスキャンし、各トークンをキャンセルし、すべてのタスクが適切に終了することを保証しながら、タスクを個別またはグループで管理する柔軟性を提供します。
結論
コード内に無限ループを設定したい場合は危険信号です。続行する前に、次のことを自問することが重要です。 キャンセルトークン このループをエレガントに処理するのがこれ以上に適切ではありません。これらのトークンを統合することで、サービスの安定性を損なったり、現在の処理を突然中断したりすることなく、サービスを適切にシャットダウンできるようになります。
この記事をお読みいただきありがとうございます。 C# で無限ループとキャンセル トークンを効果的に処理する方法をよりよく理解するのに役立つことを願っています。
ご質問や共有したいアイデアがございましたら、コメントを残してください。
C# とその環境についてさらに詳しく知りたい場合は、ぜひご参加ください。 ツイッチ!そこで定期的にライブプログラミングセッションを共有しています。
#キャンセル #トークン #無限ループをスタイリッシュに処理する