Windows の Visual Studio 2019 で .NET Core コンソール アプリケーション (Windows 10 を搭載したサーバーで Windows サービスとして実行する準備ができている) を開発しており、継続的な開発ツールは CentOS 8 (GoCD) にあります。
そのアプリケーションを Windows で公開し、サービスとして実行すると、すべてがうまくいきます。Linux で公開し、ビルドしたアプリケーションを Windows 10 で実行しようとすると、次のようになります。
未処理の例外。System.PlatformNotSupportedException: ServiceController を使用すると、Windows サービスの操作とアクセスが可能になり、他のオペレーティング システムには適用されません。/opt/gocd-agent-1/go-agent-19.12.0/pipelines/app-SCHEDULER-PRODUCTION/ の appIV.Scheduler.ServiceBaseLifetime..ctor(IApplicationLifetime applicationLifetime) の System.ServiceProcess.ServiceBase..ctor() でapp-SCHEDULER-PRODUCTION/appIV/appIV.Scheduler/Service/ServiceBaseLifetime.cs:15行目
CentOS 8 での環境:
dotnet --list-runtimes
Microsoft.AspNetCore.App 3.1.3 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.3 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
dotnet --list-sdks
3.1.201 [/usr/share/dotnet/sdk]
次のコマンドでアプリケーションを公開します
dotnet publish -o /tmp/app/scheduler/bin/Release/netcoreapp3.1/win-x64/publish/ -c Release -r win-x64 --self-contained true
-r win-x64パラメーターは、Windows をターゲットとする Linux で dotnet 公開するのに十分であると考えていましたが、明らかに間違っています。アプリケーションは、通常のコンソール アプリケーションとして実行されている限り動作しています ( --console パラメータを使用して、そのサービスをコンソール アプリケーションとして実行できます)。しかし、何らかの理由で、Linux で公開されたアプリケーションが Windows でサービスとして実行できないことを理解していません。
エラーが発生したサービスとして実行を管理するクラス:
using Microsoft.Extensions.Hosting;
using NLog;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
namespace App.Scheduler
{
public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
private NLog.Logger _logger = LogManager.GetCurrentClassLogger();
public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
private IApplicationLifetime ApplicationLifetime { get; }
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
_logger.Debug("WaitForStartAsync");
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
}
private void Run()
{
try
{
_logger.Debug("Run");
Run(this); // This blocks until the service is stopped.
_delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
}
catch (Exception ex)
{
_logger.Error(ex.Message);
_delayStart.TrySetException(ex);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.Debug("StopAsync");
Stop();
return Task.CompletedTask;
}
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args)
{
_logger.Debug("OnStart");
_delayStart.TrySetResult(null);
base.OnStart(args);
}
// Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
// That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
protected override void OnStop()
{
_logger.Debug("OnStop");
ApplicationLifetime.StopApplication();
base.OnStop();
}
}
}