👋

C#のGeneric Hostでコンソールアプリケーションっぽく使う方法

2021/06/27に公開

Generic Hostとは

日本語では汎用ホストと呼ばれ、ASP.Net Coreのホストと同じような感じでコンソールアプリケーションやWPFなどのプログラムを書くことができます。ホストでは、DIやログの記録、構成情報の取得などの便利機能をいい感じにやってくれます。公式サイト

ASP.Net Coreであれば、テンプレで自動で必要なライブラリなどをインストールしたりして意識せずとも使うことができます。しかし、コンソールアプリケーションのテンプレではホストを使う前提のテンプレになっていないため、汎用ホストを使うには必要なライブラリと処理を自分で用意する必要があります。

そこで、今回はなるべく簡単に汎用ホストを使ってコンソールアプリっぽく動かす方法を書いていきたいと思います。

手順

  1. Visual Studioの新しいプロジェクトから、「ワーカーサービス」のプロジェクトを作成する。
  2. Program.csのMainの処理を非同期に修正
  3. Worker.csのコンストラクタで必要なインスタンスを受け取る
  4. ExecuteAsyncメソッドで終了したい場所で終了処理を呼び出す

プロジェクトの作成

Visual Studioから新しいプロジェクトを作成します。
「ワーカーサービス」という名前のテンプレートを選択しましょう。
C#かF#がありますが、ここではC#の方のテンプレートを使います。
ランタイムのバージョンを聞かれると思いますが、.net core 3.1か.net 5を選択します。

メイン処理の修正

main処理は初期状態では同期処理として実行するようになっています。
しかし、実処理で非同期処理を書くと処理が完了するのを待たずに終わってしまう可能性があるので、Main内の処理を以下のように書き換えます。

public static async Task Main(string[] args)
{
    await CreateHostBuilder(args).Build().RunAsync();
}

worker.csの修正

初期状態では、worker.cs内のExecuteAsyncが実行されるようにされています。
別のクラス作って書き直してもいいのですが、作り直すのも面倒なので今回はこれを修正します。

初めにやることは、コンストラクタでIHostApplicationLifetime型のインスタンスを受け取ります。汎用ホストのDI機能で自動で入れてくれるので、ただコンストラクタで受け取れるように引数を増やせばOKです。

private readonly ILogger<Worker> _logger;
private readonly IHostApplicationLifetime _hostApplicationLifetime;

public Worker(ILogger<Worker> logger,IHostApplicationLifetime hostApplicationLifetime)
{
    _logger = logger;
    _hostApplicationLifetime = hostApplicationLifetime;
}

次にやることは、実処理で終了処理を入れることです。
BackGroundService処理では、ExecuteAsyncの処理が終わったとしても自動で処理が終わってくれません。そのため、コンソールアプリケーションのように処理が終わったらプログラムが終わるようにするためには、明示的にホストの処理を終わらせるメソッドを呼び出す必要があります。
その処理は、先ほどのIHostApplicationLifeTimeクラスのStopApplicationメソッドになります。
実際にその処理をExecuteAsync内に書いてみましょう。以下の処理ではwhile文も消しちゃいました。

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
    await Task.Delay(1000, stoppingToken);
    _hostApplicationLifetime.StopApplication();
}

実行してみると現在時刻を表示して処理が停止したと思います。(それ以外にもいろいろ表示されたと思いますが、ホストを使うと自動で出力されるものです。)
試しに_hostApplicationLifetime.StopApplication();を削除してみてください。すると現在時刻を表示した後にも処理が終了しないことが分かると思います。Ctrl+Cを押すと終わります。

まとめ

以上が汎用ホストを使う一番簡単な実装になります。(当社比)
ワーカーサービスのテンプレートを使ってますが、プロジェクトのプロパティを確認したところワーカーサービス向けの特別な設定がなく、普通のコンソールアプリケーション向けの設定と変わらなさそうなので、コンソールアプリケーションとして使っても問題ないかなと思います。
ここでは一切書いてませんが、appsettings.jsonも簡単に使えるような状態になっていますので、こちらを参考に実装すると構成ファイルを簡単に扱えるようになります。
個人的にはログの出力がコンソール以外の出力形式で簡単に扱えるのがすごくうれしいので、汎用ホストは積極的に使いたい仕組みです。また、ASP.Net Core向けに作られたミドルウェアや便利ライブラリも割と使えたりするので、コンソールアプリの公式テンプレートが汎用ホストになってくれるとうれしいなーって思います。初学者お断りのプログラミング言語になりそうな感じしちゃいますが

ただ、ちょっとめんどくささがすごいのと、処理の終わりにメソッドを呼び出さなきゃいけないのがあまりよろしくないなーって思ってしまうので、コンソールアプリケーションで汎用ホストを使いたい場合はConsoleAppFrameworkというライブラリを使うと幸せになると思います。

Discussion