简体   繁体   English

.NET 控制台应用程序作为 Windows 服务

[英].NET console application as Windows service

I have console application and would like to run it as Windows service.我有控制台应用程序并希望将其作为 Windows 服务运行。 VS2010 has project template which allow to attach console project and build Windows service. VS2010 有项目模板,允许附加控制台项目和构建 Windows 服务。 I would like to not add separated service project and if possible integrate service code into console application to keep console application as one project which could run as console application or as windows service if run for example from command line using switches.我不想添加单独的服务项目,如果可能的话,将服务代码集成到控制台应用程序中,以将控制台应用程序作为一个项目,如果使用开关从命令行运行,则该项目可以作为控制台应用程序或 Windows 服务运行。

Maybe someone could suggest class library or code snippet which could quickly and easily transform c# console application to service?也许有人可以建议可以快速轻松地将 c# 控制台应用程序转换为服务的类库或代码片段?

I usually use the following techinque to run the same app as a console application or as a service:我通常使用以下技术来运行与控制台应用程序或服务相同的应用程序:

using System.ServiceProcess

public static class Program
{
    #region Nested classes to support running as service
    public const string ServiceName = "MyService";

    public class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
    #endregion
    
    static void Main(string[] args)
    {
        if (!Environment.UserInteractive)
            // running as service
            using (var service = new Service())
                ServiceBase.Run(service);
        else
        {
            // running as console app
            Start(args);

            Console.WriteLine("Press any key to stop...");
            Console.ReadKey(true);

            Stop();
        }
    }
    
    private static void Start(string[] args)
    {
        // onstart code here
    }

    private static void Stop()
    {
        // onstop code here
    }
}

Environment.UserInteractive is normally true for console app and false for a service. Environment.UserInteractive通常对于控制台应用程序为 true,对于服务为 false。 Techically, it is possible to run a service in user-interactive mode, so you could check a command-line switch instead.从技术上讲,可以在用户交互模式下运行服务,因此您可以改为检查命令行开关。

I've had great success with TopShelf .我在TopShelf 上取得了巨大的成功。

TopShelf is a Nuget package designed to make it easy to create .NET Windows apps that can run as console apps or as Windows Services. TopShelf 是一个 Nuget 包,旨在简化创建可作为控制台应用程序或 Windows 服务运行的 .NET Windows 应用程序。 You can quickly hook up events such as your service Start and Stop events, configure using code eg to set the account it runs as, configure dependencies on other services, and configure how it recovers from errors.您可以快速连接诸如服务启动和停止事件之类的事件,使用代码进行配置,例如设置它运行的帐户,配置对其他服务的依赖关系,以及配置它如何从错误中恢复。

From the Package Manager Console (Nuget):从包管理器控制台 (Nuget):

Install-Package Topshelf安装包顶层

Refer to the code samples to get started.请参阅代码示例以开始使用。

Example:例子:

HostFactory.Run(x =>                                 
{
    x.Service<TownCrier>(s =>                        
    {
       s.ConstructUsing(name=> new TownCrier());     
       s.WhenStarted(tc => tc.Start());              
       s.WhenStopped(tc => tc.Stop());               
    });
    x.RunAsLocalSystem();                            

    x.SetDescription("Sample Topshelf Host");        
    x.SetDisplayName("Stuff");                       
    x.SetServiceName("stuff");                       
}); 

TopShelf also takes care of service installation, which can save a lot of time and removes boilerplate code from your solution. TopShelf 还负责服务安装,这可以节省大量时间并从您的解决方案中删除样板代码。 To install your .exe as a service you just execute the following from the command prompt:要将您的 .exe 安装为服务,您只需从命令提示符执行以下操作:

myservice.exe install -servicename "MyService" -displayname "My Service" -description "This is my service."

You don't need to hook up a ServiceInstaller and all that - TopShelf does it all for you.您不需要连接 ServiceInstaller 和所有这些 - TopShelf 为您完成这一切。

So here's the complete walkthrough:所以这是完整的演练:

  1. Create new Console Application project (eg MyService)创建新的控制台应用程序项目(例如 MyService)
  2. Add two library references: System.ServiceProcess and System.Configuration.Install添加两个库引用:System.ServiceProcess 和 System.Configuration.Install
  3. Add the three files printed below添加下面打印的三个文件
  4. Build the project and run "InstallUtil.exe c:\\path\\to\\MyService.exe"构建项目并运行“InstallUtil.exe c:\\path\\to\\MyService.exe”
  5. Now you should see MyService on the service list (run services.msc)现在您应该在服务列表中看到 MyService(运行 services.msc)

*InstallUtil.exe can be usually found here: C:\\windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.ex‌​e *InstallUtil.exe 通常可以在这里找到:C:\\windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.ex‌ e

Program.cs程序.cs

using System;
using System.IO;
using System.ServiceProcess;

namespace MyService
{
    class Program
    {
        public const string ServiceName = "MyService";

        static void Main(string[] args)
        {
            if (Environment.UserInteractive)
            {
                // running as console app
                Start(args);

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey(true);

                Stop();
            }
            else
            {
                // running as service
                using (var service = new Service())
                {
                    ServiceBase.Run(service);
                }
            }
        }

        public static void Start(string[] args)
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} started{1}", DateTime.Now, Environment.NewLine));
        }

        public static void Stop()
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} stopped{1}", DateTime.Now, Environment.NewLine));
        }
    }
}

MyService.cs我的服务.cs

using System.ServiceProcess;

namespace MyService
{
    class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
}

MyServiceInstaller.cs我的服务安装程序

using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyService
{
    [RunInstaller(true)]
    public class MyServiceInstaller : Installer
    {
        public MyServiceInstaller()
        {
            var spi = new ServiceProcessInstaller();
            var si = new ServiceInstaller();

            spi.Account = ServiceAccount.LocalSystem;
            spi.Username = null;
            spi.Password = null;

            si.DisplayName = Program.ServiceName;
            si.ServiceName = Program.ServiceName;
            si.StartType = ServiceStartMode.Automatic;

            Installers.Add(spi);
            Installers.Add(si);
        }
    }
}

Here is a newer way of how to turn a Console Application to a Windows Service as a Worker Service based on the latest .Net Core 3.1 .这是基于最新的.Net Core 3.1将控制台应用程序转换为 Windows 服务作为工作服务的更新方法。

If you create a Worker Service from Visual Studio 2019 it will give you almost everything you need for creating a Windows Service out of the box, which is also what you need to change to the console application in order to convert it to a Windows Service.如果您从 Visual Studio 2019 创建一个工作服务,它将为您提供开箱即用创建 Windows 服务所需的几乎所有内容,这也是您需要更改控制台应用程序以将其转换为 Windows 服务的内容。

Here are the changes you need to do:以下是您需要做的更改:

Install the following NuGet packages安装以下 NuGet 包

Install-Package Microsoft.Extensions.Hosting.WindowsServices -Version 3.1.0
Install-Package Microsoft.Extensions.Configuration.Abstractions -Version 3.1.0

Change Program.cs to have an implementation like below:将 Program.cs 更改为具有如下实现:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).UseWindowsService().Build().Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}

and add Worker.cs where you will put the code which will be run by the service operations:并添加 Worker.cs ,您将在其中放置将由服务操作运行的代码:

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class Worker : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            //do some operation
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return base.StartAsync(cancellationToken);
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            return base.StopAsync(cancellationToken);
        }
    }
}

When everything is ready, and the application has built successfully, you can use sc.exe to install your console application exe as a Windows Service with the following command:当一切准备就绪并且应用程序已成功构建后,您可以使用sc.exe使用以下命令将控制台应用程序 exe 安装为 Windows 服务:

sc.exe create DemoService binpath= "path/to/your/file.exe"

Firstly I embed the console application solution into the windows service solution and reference it.首先我将控制台应用方案嵌入到windows服务方案中并引用。

Then I make the console application Program class public然后我将控制台应用程序 Program 类设为 public

/// <summary>
/// Hybrid service/console application
/// </summary>
public class Program
{
}

I then create two functions within the console application然后我在控制台应用程序中创建两个函数

    /// <summary>
    /// Used to start as a service
    /// </summary>
    public void Start()
    {
        Main();
    }

    /// <summary>
    /// Used to stop the service
    /// </summary>
    public void Stop()
    {
       if (Application.MessageLoop)
            Application.Exit();   //windows app
        else
            Environment.Exit(1);  //console app
    }

Then within the windows service itself I instantiate the Program and call the Start and Stop functions added within the OnStart and OnStop.然后在 Windows 服务本身中,我实例化程序并调用在 OnStart 和 OnStop 中添加的启动和停止函数。 See below见下文

class WinService : ServiceBase
{
    readonly Program _application = new Program();

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        ServiceBase[] servicesToRun = { new WinService() };
        Run(servicesToRun);
    }

    /// <summary>
    /// Set things in motion so your service can do its work.
    /// </summary>
    protected override void OnStart(string[] args)
    {
        Thread thread = new Thread(() => _application.Start());
        thread.Start();
    }

    /// <summary>
    /// Stop this service.
    /// </summary>
    protected override void OnStop()
    {
        Thread thread = new Thread(() => _application.Stop());
        thread.Start();
    }
}

This approach can also be used for a windows application / windows service hybrid这种方法也可以用于windows应用程序/windows服务混合

I hear your point at wanting one assembly to stop repeated code but, It would be simplest and reduce code repetition and make it easier to reuse your code in other ways in future if...... you to break it into 3 assemblies.我听到了您希望一个程序集停止重复代码的观点,但是,如果……您将其分解为 3 个程序集,那么这将是最简单的并减少代码重复,并且将来可以更轻松地以其他方式重用您的代码。

  1. One library assembly that does all the work.一个可以完成所有工作的库程序集。 Then have two very very slim/simple projects:然后有两个非常非常苗条/简单的项目:
  2. one which is the commandline一个是命令行
  3. one which is the windows service.一个是windows服务。

You can use您可以使用

reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v ServiceName /d "c:\path\to\service\file\exe"

And it will appear int the service list.它将出现在服务列表中。 I do not know, whether that works correctly though.我不知道,这是否正常工作。 A service usually has to listen to several events.一个服务通常必须监听多个事件。

There are several service wrapper though, that can run any application as a real service.不过,有几个服务包装器可以将任何应用程序作为真正的服务运行。 For Example Microsofts SrvAny from the Win2003 Resource Kit例如,来自Win2003 Resource Kit 的Microsofts SrvAny

I use a service class that follows the standard pattern prescribed by ServiceBase , and tack on helpers to easy F5 debugging.我使用遵循ServiceBase规定的标准模式的服务类,并添加帮助器以轻松 F5 调试。 This keeps service data defined within the service, making them easy to find and their lifetimes easy to manage.这将在服务中定义服务数据,使其易于查找并易于管理其生命周期。

I normally create a Windows application with the structure below.我通常创建一个具有以下结构的 Windows 应用程序。 I don't create a console application;我不创建控制台应用程序; that way I don't get a big black box popping in my face every time I run the app.这样我每次运行应用程序时都不会在我的脸上弹出一个大黑匣子。 I stay in in the debugger where all the action is.我留在所有操作所在的调试器中。 I use Debug.WriteLine so that the messages go to the output window, which docks nicely and stays visible after the app terminates.我使用Debug.WriteLine以便消息转到输出窗口,该窗口很好地停靠并在应用程序终止后保持可见。

I usually don't bother add debug code for stopping;我通常不会为停止添加调试代码; I just use the debugger instead.我只是使用调试器。 If I do need to debug stopping, I make the project a console app, add a Stop forwarder method, and call it after a call to Console.ReadKey .如果我确实需要调试停止,我将项目设为控制台应用程序,添加Stop转发器方法,并在调用Console.ReadKey后调用它。

public class Service : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        // Start logic here.
    }

    protected override void OnStop()
    {
        // Stop logic here.
    }

    static void Main(string[] args)
    {
        using (var service = new Service()) {
            if (Environment.UserInteractive) {
                service.Start();
                Thread.Sleep(Timeout.Infinite);
            } else
                Run(service);
        }
    }
    public void Start() => OnStart(null);
}

Maybe you should define what you need, as far as I know, you can't run your app as Console or Service with command line, at the same time.也许你应该定义你需要的东西,据我所知,你不能同时使用命令行将你的应用程序作为控制台或服务运行。 Remember that the service is installed and you have to start it in Services Manager, you can create a new application wich starts the service or starts a new process running your console app.请记住,该服务已安装,您必须在服务管理器中启动它,您可以创建一个新应用程序来启动该服务或启动一个运行控制台应用程序的新进程。 But as you wrote但正如你写的

"keep console application as one project" “将控制台应用程序作为一个项目”

Once, I was in your position, turning a console application into a service.曾经,我在您的位置上,将控制台应用程序转变为服务。 First you need the template, in case you are working with VS Express Edition.首先,您需要模板,以防您使用 VS Express Edition。 Here is a link where you can have your first steps: C# Windows Service , this was very helpful for me.这是一个链接,您可以在其中进行第一步: C# Windows Service ,这对我非常有帮助。 Then using that template, add your code to the desired events of the service.然后使用该模板,将您的代码添加到服务的所需事件中。

To improve you service, there's another thing you can do, but this is not quick and/or easily, is using appdomains, and creating dlls to load/unload.为了改善您的服务,您还可以做另一件事,但这不是快速和/或容易的,就是使用 appdomains,并创建 dll 来加载/卸载。 In one you can start a new process with the console app, and in another dll you can just put the functionality the service has to do.在一个中,您可以使用控制台应用程序启动一个新进程,而在另一个 dll 中,您可以只放置服务必须执行的功能。

Good luck.祝你好运。

You need to seperate the functionality into a class or classes and launch that via one of two stubs.您需要将功能分离到一个或多个类中,然后通过两个存根之一启动它。 The console stub or service stub.控制台存根或服务存根。

As its plain to see, when running windows, the myriad services that make up the infrastructure do not (and can't directly) present console windows to the user.显而易见,在运行 Windows 时,构成基础设施的无数服务不会(也不能直接)向用户显示控制台窗口。 The service needs to communicate with the user in a non graphical way: via the SCM;服务需要以非图形方式与用户通信:通过 SCM; in the event log, to some log file etc. The service will also need to communicate with windows via the SCM, otherwise it will get shutdown.在事件日志中,到一些日志文件等。该服务还需要通过 SCM 与 windows 通信,否则它将被关闭。

It would obviously be acceptable to have some console app that can communicate with the service but the service needs to run independently without a requirement for GUI interaction.拥有一些可以与服务通信但服务需要独立运行而不需要 GUI 交互的控制台应用程序显然是可以接受的。

The Console stub can very useful for debugging service behaviour but should not be used in a "productionized" environment which, after all, is the purpose of creating a service.控制台存根对于调试服务行为非常有用,但不应在“生产化”环境中使用,毕竟这是创建服务的目的。

I haven't read it fully but this article seems to pint in the right direction.我还没有完全阅读它,但这篇文章似乎朝着正确的方向发展。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM