开机启动和windows服务的区别

  • 将应用程序或者脚本等文件设置为开机启动时,需要用户登录系统时才回去执行,这可能还需要用户输入开机密码或者赋予程序和脚本的管理员执行权限等额外操作。
  • windows服务则不同,它是随系统一起启动的,我们不需要额外的用户操作,再执行一些自动化操作时,注册服务显然比开机启动项更占优势。

服务注册

windows系统中,自带了SC服务管理程序,它是一个服务控制管理器和服务进行通信的命令行程序。

image-20221126222729064

使用sc create 命令来创建一个服务,示例命令如下:

#binPath指定应用程序路径,start参数指定启动方式,displayname指定服务显示的名称
sc create MyService binPath="M:\Projects\Redir\Test\Test\bin\Debug\net6.0\Test.exe" start=auto displayname="CocoService"

添加服务描述

使用sc description命令为注册服务添加描述,示例命令如下:

sc description MyService "CocoService描述"

启动服务

使用sc start命令启动服务,示例命令如下:

image-20221126223836647

🍕可以看到服务启动报错,为什么呢,我们先看一下Test.exe这个程序是干嘛的,代码如下:

while (true)
{
    //每隔十秒将当前时间写入到文件中
    Thread.Sleep(1000 * 10);
    File.AppendAllText("time.txt", DateTime.Now.ToLocalTime().ToString());
}

代码很简单就是一个很普通的控制台程序,每隔十秒将当前时间写入到名为time.txt的文件中,看了一下windows服务的应用程序日志,也有系统错误日志,但未指出错误原因;网上查询了好久,终于在一个评论区看到一条回复:sc程序无法将普通应用程序或者批处理文件注册为系统服务,windows服务有特有的编写方式。

正确姿势

使用Work Service项目来编写windows服务,首先创建一个项目

image-20221128202650351

程序入口文件内容如下:

using MyService;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services => { services.AddHostedService<Worker>(); })
    .Build();

await host.RunAsync();

Asp.NetCore项目非常相似,创建一个宿主主机来托管我们的程序,重点来了,要创建windows服务我们需要依赖一个NuGet

image-20221128203258329

它提供了IHostBuilder的一个扩展方法,让我们将用程序注册为windows服务,看一下默认的Worker服务

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

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

它继承了BackgroundService类,然后重写了ExecuteAsync方法,我们要后台运行的代码就放到这个方法中由托管主机来调用,书接上文,编写一段每隔10秒将当前时间写入到文档中的方法

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            File.AppendAllText(AppContext.BaseDirectory + "time.txt", DateTime.Now.ToLocalTime() + "\n");
            await Task.Delay(1000, stoppingToken);
        }
    }

修改入口程序

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services => { services.AddHostedService<Worker>(); })
    .UseWindowsService(options =>
    {
        options.ServiceName = "MyService";
    } )
    .Build();


await host.RunAsync();

调用UseWindowsService方法并声明服务名称。OK,程序编写完毕,我们将项目发布,然后再使用SC来创建windows服务

image-20221128205554818

需要注意的是使SC创建服务时,服务名必须和UseWindowsService方法指定的服务名一致,=后面必须接一个空格。接下来启动服务

image-20221128205940838

再看下服务面板

image-20221128210030406

验证一下程序运行是否正常,查看time.txt文件内容

image-20221128210155035