张子阳的博客

首页 读书 技术 店铺 关于
张子阳的博客 首页 读书 技术 关于

.Net Core 在Windows上后台运行Console控制台程序

2019-01-18 张子阳 分类: .Net Core

有时候,我们需要在后台运行程序。通常,在Linux上,可以做成系统服务,使用systemctl命令来管理程序的启动和运行;也可以使用 nohup [应用程序] & 来后台运行。在Windows上,想要后台执行程序,通常只能做成Windows服务。有时候,做成Windows服务比较麻烦,当使用.Net Core时,需要将.Net Core程序托管在Windows服务中。但是,可以使用一个间接的方法,让.Net Core控制台程序后台运行。

首先新建一个.Net Core控制台程序,这个程序管它叫AppStarter,它的主要作用就是启动其他程序,然后在启动时通过配置 CreateNoWindow 和 UseShellExecute 来设置实际要启动的控制台程序的启动方式。

在这个例子中,我们的启动器将要启动的程序叫做DataSync.dll,是一个用来做数据同步的小程序,接收的参数为要同步的数据库名称。因此,可以像下面这样,编写一个配置文件appsettings.json:

{
    "assembly": "/Users/zhangzy/code/dotnet/MSSQL数据库同步/DataSync/pub/DataSync.dll",
    "databases": ["ShopUser", "ShopLog", "ShopCenter"],
    "CreateNoWindow": true,
    "UseShellExecute": false
}

接下来,编写Program.cs:

using System;
using System.Diagnostics;
using System.IO;
using Microsoft.Extensions.Configuration;
using System.Linq;

namespace DataSyncStarter
{
    class Program
    {
        static IConfigurationRoot config;
        static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger ();

        static void Main(string[] args)
        {
            string env = Environment.GetEnvironmentVariable ("ASPNETCORE_ENVIRONMENT");
            var builder = new ConfigurationBuilder ()
                .SetBasePath (Path.Combine (AppContext.BaseDirectory))
                .AddJsonFile ("appsettings.json", false)
                .AddJsonFile ($"appsettings.{ env }.json", optional : true);

            config = builder.Build ();
            SetLogConfig();
            
            var databases = config.GetSection("databases").GetChildren().Select(x=> x.Value).ToArray();
            var assembly = config["assembly"];
            logger.Info($"控制台启动器运行, 环境:{ env }, 同步数据库:{ string.Join(", ", databases) }, CreateNoWindow:{Convert.ToBoolean(config["CreateNoWindow"])}, UseShellExecute:{Convert.ToBoolean(config["UseShellExecute"])}");
            
            foreach(string db in databases) {
                int pid = StartProcess(assembly, db);
                var fi = new FileInfo(assembly);
                logger.Info($"开启应用: {fi.Name} {db},进程id:{ pid }");
            }
        }

         // 开启进程
        static int StartProcess (string assembly, string db) {
            var proc = new Process ();
            proc.StartInfo.FileName = "dotnet";
            proc.StartInfo.Arguments = assembly + " " + db;
            proc.StartInfo.CreateNoWindow = Convert.ToBoolean(config["CreateNoWindow"]);
            proc.StartInfo.UseShellExecute = Convert.ToBoolean(config["UseShellExecute"]);
            proc.Start();
            return proc.Id;
        }

        static void SetLogConfig(){
            var nlogConf = new NLog.Config.LoggingConfiguration();
            string layout = "${date:format=HH\\:mm\\:ss}|${level}|${message} ${exception}";
            var logfile = new NLog.Targets.FileTarget("logfile"){
                Layout = layout,
                FileName = "${basedir}/logs/${shortdate}.log"
            };
            var logconsole = new NLog.Targets.ConsoleTarget("logconsole"){
                Layout = layout
            };
            nlogConf.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logfile);
            nlogConf.AddRule(NLog.LogLevel.Info, NLog.LogLevel.Fatal, logconsole);

            NLog.LogManager.Configuration = nlogConf;
            NLog.LogManager.ReconfigExistingLoggers();
        }
    }
}

这里最主要的是StartProcess()方法中的参数设置,有下面几种配置方式:

Windows下配置项的不同结果
  CreateNoWindow UseShellExecute 结果
1. false true 新建一个窗口,然后在新建的窗口运行子进程。主进程正常退出,子进程继续运行。
2. false false 在当前窗口运行子进程。关闭当前窗口后,主进程退出,开启的子进程也退出。
3. true false 当前窗口主进程正常退出。子进程在后台执行,不开启新窗口,需要通过任务管理器关闭子进程。
4. true true 和第1种情况相同

从上面可见,对于这里的需求而言,第3中情况比较符合。

因为使用了NLog以及Configuration,如果要运行程序,需要在项目文件中添加一下引用的程序集:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0"/>
    <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0"/>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0"/>
    <PackageReference Include="NLog" Version="4.5.10"/>
  </ItemGroup>
  <ItemGroup>
    <Content Include="*.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="*.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>

最后,运行程序:

# dotnet AppStarter.dll
11:58:51|Info|控制台启动器运行, 环境:Development, 同步数据库:ShopUser, ShopLog, ShopCenter, CreateNoWindow:True, UseShellExecute:False 
11:58:51|Info|开启应用: DataSync.dll ShopUser,进程id:7612 
11:58:51|Info|开启应用: DataSync.dll ShopLog,进程id:7316 
11:58:51|Info|开启应用: DataSync.dll ShopCenter,进程id:4868 

可以看到类似下面的结果:

后台运行的Console程序

在上图中,我后台运行了6个控制台程序(注意代码中只有3个,这里有点图文不符)。

感谢阅读,希望这篇文章能给你带来帮助!