我在 Windows 上的 Visual Studio 2019 中开发 .NET Core 控制台应用程序(准备在装有 Windows 10 的服务器上作为 Windows 服务运行),我的持续开发工具在 CentOS 8 (GoCD) 上。
当我在 Windows 上发布该应用程序并作为服务运行时,一切都很好。当我尝试在 Linux 上发布并在 Windows 10 上运行该构建的应用程序时,我得到:
未处理的异常。System.PlatformNotSupportedException:ServiceController 允许操作和访问 Windows 服务,它不适用于其他操作系统。在 System.ServiceProcess.ServiceBase..ctor() 在 appIV.Scheduler.ServiceBaseLifetime..ctor(IApplicationLifetime applicationLifetime) 在 /opt/gocd-agent-1/go-agent-19.12.0/pipelines/app-SCHEDULER-PRODUCTION/ app-SCHEDULER-PRODUCTION/appIV/appIV.Scheduler/Service/ServiceBaseLifetime.cs:line 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();
}
}
}