ConsoleAppFrameworkでasync void
なコマンドは実行しても正常に完了できません。async Taskを使いましょう。以上です。
どのような挙動になるかを説明しておきます。
ConsoleAppFrameworkのCommand
ConsoleAppFrameworkのコマンドの返り値は次のいずれかを期待しています。エラー時は例外にするならvoid
やasync Task
、ExitCodeを調整したいときはint
やasync Task<int>
を使うという使い分けです。
void
async Task
int
async Task<int>
たとえば同期処理しかないコマンドは次のように書けます。
[Command("hello")] public void Hello(string name) { Console.WriteLine($"Hello {name}!"); }
$ dotnet run ConsoleApp1 -- hello foo
Hello foo!
たとえば非同期処理を含むコマンドなら次のように書けます。
[Command("hello-async")] public async Task AsyncTask(string name) { var ts = TimeProvider.System.GetTimestamp(); Console.WriteLine($"Hello {name}!"); await Task.Delay(1000); Console.WriteLine($"Waited {TimeProvider.System.GetElapsedTime(ts)}"); }
$ dotnet run ConsoleApp1 -- hello-async foo Hello foo! Waited 00:00:01.0050441
async voidの問題
async Task
と書くつもりで間違えてasync void
にすることはまれに時々あります。1
ではasync void
にしたコマンドはasync Task
とどのように挙動が違うか見てみましょう。
// async Taskをasync voidに変更しただけ [Command("hello-async-void")] public async void AsyncVoid(string name) { Console.WriteLine($"Hello {name}!"); await Task.Delay(1000); Console.WriteLine($"Waited"); }
async void
だと非同期処理が完了する前にコマンドが終了してしまいます。このため、await Task.Delay(1000)
を待つことなくコマンドがそこで終了するのでWaited...
が表示されません。
$ dotnet run ConsoleApp1 -- hello-async-void foo
Hello foo!
IDEやビルド時に気づければよいのですが、5.3.3まではasync void
なコマンドでも何事もなくビルド、実行できるので気づくのに遅れるでしょう。
また、デバッグ実行してもawaitの行でステップ実行を進めるとデバッガーが終了します。これはasync void
あるあるですね。
対策
async Task
にしましょう。
また、2025/1/16にリリースされた5.3.4でasync void
コマンドはアナライザーがエラーを出してビルドできくなりました。2今ConsoleAppFramework v5を使っている人は、このバージョンにアップグレードしておきましょう。