C# Worker Service的基础应用

这篇具有很好参考价值的文章主要介绍了C# Worker Service的基础应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

Worker Service是微软提供的一个项目模板,它继承于BackgroundService。在使用.NET Framework时,我们可以使用Windows Service来创建Windows服务,而在使用.NET Core及更高的.NET版本时,就可以使用Worker Service来取代Windows Service,以便创建跨平台服务。

在微软的.NET文档的运行时库(Runtime libraries)分支中,辅助角色服务(Worker Services)详细介绍了Worker Service的使用。

以下为微软文档的链接:

.NET 中的辅助角色服务 | Microsoft Docs

本文代码已上传至GitHub,项目链接如下:

https://github.com/XMNHCAS/WorkerServiceDemo


创建项目

打开Visual Studio,创建一个新的Worker Service项目,如下图所示:

C# Worker Service的基础应用

C# Worker Service的基础应用

 C# Worker Service的基础应用

创建完成后,直接运行,会出现一个控制台窗口,并打印出运行信息:

C# Worker Service的基础应用

项目创建成功后,Windows会预设一个Worker Service的模板,它主要文件为Program.cs和Worker.cs,其中Program.cs提供了服务的启动入口,而Worker则是服务的具体功能实现。

打开Program.cs,可以看到,它使用了CreateHostBuilder方法返回了一个添加了Worker类作为托管服务的host,然后调用它的Build方法生成实例并调用Run方法来运行该实例。整个Program.cs的结构与普通的控制台应用程序基本相同。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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

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

实际上,Worker Service在部署完成后,就是一个没有窗口的控制台应用程序。在开发过程中,Worker Service的输出控制台界面使得它较Windows Service更容易调试。


基础应用

应用过程

接下来我们来使用Worker Service创建一个简单Demo服务,具体实现功能就是在一个日志文件中定时记录一些消息。

首先我们创建一个InfoLogWorker类,并使其继承于BackgroundService类,并将Program配置的服务改成InfoLogWorker。

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

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

可以看到,BackgroundService提供了三个虚方法和一个抽象方法,我们需要对StartAsync、StopAsync和ExecuteAsync三个方法进行重写。

C# Worker Service的基础应用

其中StartAsync会在服务开启时被调用,而StopAsync则是在服务停止时被调用,ExecuteAsync是必须要被实现的方法,它定义了服务运行时需要执行的操作。

在重写完成这个三个方法之后,我们再创建一个ILogger的只读字段,并在构造函数中获取它的实例,之后将会使用它在调试时打印出我们需要的信息。然后我们再定义一个ServiceRunningMethod方法,用以定义服务运行时需要实现的具体功能。

具体定义如下:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerServiceDemo
{
    class LogInfoWorker : BackgroundService
    {
        /// <summary>
        /// 日志字段
        /// </summary>
        private readonly ILogger<LogInfoWorker> logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="hostApplicationLifetime"></param>
        public LogInfoWorker(ILogger<LogInfoWorker> logger) => this.logger = logger;

        /// <summary>
        /// 服务启动时执行的操作
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            await base.StartAsync(cancellationToken);
        }

        /// <summary>
        /// 服务运行时执行的操作
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Task task = ServiceRunningMethod(stoppingToken);
            await Task.WhenAll(task);
        }

        /// <summary>
        /// 服务停止时执行的操作
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            await base.StopAsync(cancellationToken);
        }

        /// <summary>
        /// 运行时执行的方法
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        private Task ServiceRunningMethod(CancellationToken stoppingToken)
        {
            return Task.Run(() => { }, stoppingToken);
        }
    }
}

接下来我们需要在定义好的方法中,实现运行日志的记录。首先定义日志文件输出目录的只读字段,并创建FileOperation方法,用以实现日志操作。

/// <summary>
/// 日志输出目录
/// </summary>
private readonly string LogPath = $"{AppDomain.CurrentDomain.BaseDirectory}LogInfo.log";
/// <summary>
/// 文件操作
/// </summary>
/// <param name="fileMode">操作类型</param>
/// <param name="methodName">调用此方法的方法</param>
/// <param name="message">写入的消息</param>
private void FileOperation(FileMode fileMode, string methodName, string message)
{
    FileStream fs = new FileStream(LogPath, fileMode, FileAccess.Write);
    StreamWriter sw = new StreamWriter(fs);
    sw.WriteLine($"{DateTime.Now} : [{methodName}] {message}");
    sw.Close();
    fs.Close();
}

然后在StartAsync和StopAsync中分别添加记录操作。

/// <summary>
/// 服务启动时执行的操作
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StartAsync(CancellationToken cancellationToken)
{
    FileOperation(FileMode.OpenOrCreate, "StartAsync", "Service started.");
    logger.LogInformation($"{DateTime.Now} : Service has been requested to start.");

    await base.StartAsync(cancellationToken);
}
/// <summary>
/// 服务停止时执行的操作
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StopAsync(CancellationToken cancellationToken)
{
    logger.LogInformation($"{DateTime.Now} : Service has been requested to stop.");
    await base.StopAsync(cancellationToken);
}

最后定义运行时的操作,在服务被停止的时候,退出循环,并记录服务已停止。我们在ExecuteAsync方法中创建一个新的Task实例来调用ServiceRunningMethod,并通过Task.WhenAll(),使得ServiceRunningMethod在服务被停止之前被一直执行。

/// <summary>
/// 运行时执行的方法
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
private Task ServiceRunningMethod(CancellationToken stoppingToken)
{
    return Task.Run(() =>
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            //记录日志
            logger.LogInformation($"{DateTime.Now} : Service is running.");
            FileOperation(FileMode.Append, "ServiceRunningMethod", "Service is running.");
            Thread.Sleep(2000);
        }

        logger.LogInformation($"{DateTime.Now} : Service stopped.");
        //记录服务已停止
        FileOperation(FileMode.Append, "ServiceRunningMethod", "Service stopped.");

    }, stoppingToken);
}

运行结果如下,服务成功运行,而且日志也被记录: C# Worker Service的基础应用

虽然我们可以通过在ServiceRunningMethod退出循环后加入结束操作,但是假如我们需要在服务运行时使用多个子线程来执行不同的操作,如果在其中任一子线程中来加入这样的结束操作,就有可能会出现错误。此时我们可以通过调用IHostApplicationLifetime接口,来阻止服务停止,并执行我们需要的结束操作,然后在等待结束操作完成后手动停止服务。

首先先创建IHostApplicationLifetime的只读字段,并改写构造函数,添加IHostApplicationLifetime的参数,注入创建IHostApplicationLifetime实例。

/// <summary>
/// 应用程序生命周期字段
/// </summary>
private readonly IHostApplicationLifetime hostApplicationLifetime;

/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger"></param>
/// <param name="hostApplicationLifetime"></param>
public LogInfoWorker(ILogger<LogInfoWorker> logger, IHostApplicationLifetime hostApplicationLifetime) =>
    (this.logger, this.hostApplicationLifetime) = (logger, hostApplicationLifetime);

接着将ServiceRunningMethod中的停止操作分离为一个单独的方法。

/// <summary>
/// 运行时执行的方法
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
private Task ServiceRunningMethod(CancellationToken stoppingToken)
{
    return Task.Run(() =>
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            //记录日志
            logger.LogInformation($"{DateTime.Now} : Service is running.");
            FileOperation(FileMode.Append, "ServiceRunningMethod", "Service is running.");
            Thread.Sleep(2000);
        }
    }, stoppingToken);
}

/// <summary>
/// 服务结束前的操作
/// </summary>
private void BeforeStopMethod()
{
    logger.LogInformation($"{DateTime.Now} : Service is stopping.");

    Thread.Sleep(3000);
    logger.LogInformation($"{DateTime.Now} : Service stopped.");
    //记录服务已停止
    FileOperation(FileMode.Append, "ServiceRunningMethod", "Service stopped.");
}

然后改写ExecuteAsync,使得服务在被停止前,先执行BeforeStopMethod,再手动将服务停止。

/// <summary>
/// 服务运行时执行的操作
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    try
    {
        Task task = ServiceRunningMethod(stoppingToken);
        await Task.WhenAll(task);
    }
    catch (Exception ex)
    {
        logger.LogError(ex.ToString());
        FileOperation(FileMode.Append, "ExecuteAsync", ex.ToString());
    }
    finally
    {
        BeforeStopMethod();

        //手动停止服务
        hostApplicationLifetime.StopApplication();
    }
}

最后运行结果如下:

C# Worker Service的基础应用到这里,我们的Demo服务就已经完成了。

LogInfoWorker.cs完整代码

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerServiceDemo
{
    class LogInfoWorker : BackgroundService
    {
        /// <summary>
        /// 日志字段
        /// </summary>
        private readonly ILogger<LogInfoWorker> logger;

        /// <summary>
        /// 应用程序生命周期字段
        /// </summary>
        private readonly IHostApplicationLifetime hostApplicationLifetime;

        /// <summary>
        /// 日志输出目录
        /// </summary>
        private readonly string LogPath = $"{AppDomain.CurrentDomain.BaseDirectory}LogInfo.log";

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="hostApplicationLifetime"></param>
        public LogInfoWorker(ILogger<LogInfoWorker> logger, IHostApplicationLifetime hostApplicationLifetime) => 
            (this.logger, this.hostApplicationLifetime) = (logger,hostApplicationLifetime);

        /// <summary>
        /// 服务启动时执行的操作
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            FileOperation(FileMode.OpenOrCreate, "StartAsync", "Service started.");
            logger.LogInformation($"{DateTime.Now} : Service has been requested to start.");

            await base.StartAsync(cancellationToken);
        }

        /// <summary>
        /// 服务运行时执行的操作
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                Task task = ServiceRunningMethod(stoppingToken);
                await Task.WhenAll(task);
            }
            catch (Exception ex)
            {
                logger.LogError(ex.ToString());
                FileOperation(FileMode.Append, "ExecuteAsync", ex.ToString());
            }
            finally
            {
                BeforeStopMethod();

                //手动停止服务
                hostApplicationLifetime.StopApplication();
            }           
        }

        /// <summary>
        /// 服务停止时执行的操作
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            logger.LogInformation($"{DateTime.Now} : Service has been requested to stop.");
            await base.StopAsync(cancellationToken);
        }

        /// <summary>
        /// 运行时执行的方法
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        private Task ServiceRunningMethod(CancellationToken stoppingToken)
        {
            return Task.Run(() =>
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    //记录日志
                    logger.LogInformation($"{DateTime.Now} : Service is running.");
                    FileOperation(FileMode.Append, "ServiceRunningMethod", "Service is running.");
                    Thread.Sleep(2000);
                }              
            }, stoppingToken);
        }

        /// <summary>
        /// 服务结束前的操作
        /// </summary>
        private void BeforeStopMethod()
        {
            logger.LogInformation($"{DateTime.Now} : Service is stopping.");

            Thread.Sleep(3000);
            logger.LogInformation($"{DateTime.Now} : Service stopped.");
            //记录服务已停止
            FileOperation(FileMode.Append, "ServiceRunningMethod", "Service stopped.");
        }

        /// <summary>
        /// 文件操作
        /// </summary>
        /// <param name="fileMode">操作类型</param>
        /// <param name="methodName">调用此方法的方法</param>
        /// <param name="message">写入的消息</param>
        private void FileOperation(FileMode fileMode, string methodName, string message)
        {
            FileStream fs = new FileStream(LogPath, fileMode, FileAccess.Write);
            StreamWriter sw = new StreamWriter(fs);
            sw.WriteLine($"{DateTime.Now} : [{methodName}] {message}");
            sw.Close();
            fs.Close();
        }
    }
}

Windows环境下部署

命令

以下为部署需要用到的命令:

说明 命令
安装服务 sc.exe create 服务名 binPath=路径
卸载服务 sc.exe delete 服务名
启动服务 sc.exe start 服务名
停止服务 sc.exe stop 服务名
设置服务自动启动 sc.exe config 服务名 start= auto
设置服务手动启动 sc.exe config 服务名 start= demand
查看服务状态 sc.exe query 服务名

需要注意,上面所有的命令都需要具有管理员权限的CMD或者PowerShell来进行执行。 

NuGet程序包引用

Worker Service依赖的.NET框架为.NET Core3.1及以上,所以Worker Service项目是可以跨平台部署的,不过需要针对不同平台下载不同的包。针对Windows平台,我们需要使用NuGet添加Microsoft.Extensions.Hosting.WindowsServic程序包。在添加时,需要注意安装的版本,目前这个程序包的最新版本为6.0.0,适配与.NET6的项目,而本文实例使用的是.NET5,所以安装5.0.1版本的程序包。

C# Worker Service的基础应用

在添加完成后,我们在Program.cs的CreateHostBuilder方法,添加UseWindowsService(),这样生成的可执行文件就可以作为服务被部署到Windows系统里了。

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseWindowsService() //配置为Windows服务
        .ConfigureServices((hostContext, services) =>
        {
                    services.AddHostedService<LogInfoWorker>();
        });

如果是在Linux上部署,就是引用Microsoft.Extensions.Hosting.Systemd程序包,然后在在Program.cs的CreateHostBuilder方法,添加.UseSystemd(),最后发布为Linux版本的可执行文件即可,然后用将生成出来的文件放到Linux系统中,按照Linux的普通服务部署即可。

安装与卸载

安装服务首先需要生成可执行文件。而生成可部署的可执行文件有两种方法,一种是发布,一种是Release模式生成。微软官方文档演示的是发布,故这里不赘述。此处使用Release生成的文件。

将生成模式调成Release,然后右击项目选择生成,在项目的文件所在位置的bin文件夹内会生成一个Release文件夹,里面就是生成好的可执行文件。将这个文件夹拷贝到D盘。

C# Worker Service的基础应用

打开Windows PowerShell(管理员),执行安装命令:

sc.exe create wsdemo binPath=D:\Release\net5.0\WorkerServiceDemo.exe

C# Worker Service的基础应用

打开运行,输入services.msc,打开服务管理器,可以找到我们刚刚安装成功的wsdemo服务。

C# Worker Service的基础应用

再回到PowerShell,执行卸载命令:

sc.exe delete wsdemo

C# Worker Service的基础应用

 切回到服务管理窗口,刷新一下,我们刚刚安装了的wsdemo就已经消失了。

启动与停止

在服务安装完成后,我们可以用以下命令来实现服务的启动与停止。如果在上面卸载了服务,那么再安装一次即可。

启动服务:sc.exe start wsdemo

停止服务:sc.exe stop wsdemo

执行启动服务命令后,打开服务管理窗口,可以看到服务已经成功运行。

C# Worker Service的基础应用

执行停止命令后的,打开服务管理窗口,并打开wsdemo服务的属性,可以看到该服务已经停止。

C# Worker Service的基础应用

回到程序的目录,可以看到生成了一个LogInfo.log文件,也成功记录的服务运行的信息,说明服务启停及运行正常。 C# Worker Service的基础应用

在Windows下,启动服务和停止服务也可以直接使用服务管理器来手动启停。

设置自动启动与手动启动

将服务设置为自动启动和手动启动的命令如下:

自动启动:sc.exe config wsdemo start= auto

手动启动:sc.exe config wsdemo start= demand

需要注意,“=” 后面最好加上空格,否则在win10以下版本的系统可能会报错。

执行设置自动启动命令,打开服务管理窗口,可以看到服务被切换成自动启动。

C# Worker Service的基础应用

执行设置手动启动命令,打开服务管理窗口,可以看到服务被切换成手动启动。

C# Worker Service的基础应用

当然这个自动和手动也是可以在服务管理窗口进行手动设置的,使用命令的方式更适合批处理部署。


总结

本文介绍了Worker Service的基础应用,实际使用时可根据这个思路进行功能扩展。微软官方文档有更加详细的说明及示例,如果需要再深入了解,可以多参考微软官方文档。文章来源地址https://www.toymoban.com/news/detail-440244.html

到了这里,关于C# Worker Service的基础应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# DataGridView控件的基础应用实例

      DataGridView我把他叫做网格数据控件 。我们在显示表格数据的时候,经常会用想到用它, 他就像Excel表格一样。我们知道只要是数据表,就一定逃不掉表的增删查改操作。   该篇,我在VS2019的环境下通过demo实例来实现DataGridView控件的一系列功能,包括添加一行数据、切

    2023年04月09日
    浏览(48)
  • pytorch 分布式 Node/Worker/Rank等基础概念

    分布式训练相关基本参数的概念如下: Definitions Node  - A physical instance or a container; maps to the unit that the job manager works with. Worker  - A worker in the context of distributed training. WorkerGroup  - The set of workers that execute the same function (e.g. trainers). LocalWorkerGroup  - A subset of the workers in the worker g

    2024年02月02日
    浏览(48)
  • 【C#基础】chatGpt带你学C#接口,它在游戏中有什么应用?

    In computer programming, an interface is a set of rules or guidelines that define how different software components or systems should interact with each other. It serves as a contract between two or more components, specifying how they should communicate with each other without revealing the underlying implementation details. An interface defines a standardi

    2023年04月21日
    浏览(35)
  • C#程序设计之windows应用程序设计基础

    题目描述 设计一个“简单通讯录”程序,在窗体上建立一个下拉式列表框、两个文本框和两个标签,实现以下功能:当用户在下拉式列表框中选择一个学生姓名后,在“学生姓名”、“地址”两个文本框中分别显示出对应的学生和地址。 代码 窗体代码 运行结果 题目描述 设

    2024年02月06日
    浏览(50)
  • 使用 GPT4 和 ChatGPT 开发应用:前言到第三章

    原文:Developing Apps with GPT-4 and ChatGPT 译者:飞龙 协议:CC BY-NC-SA 4.0 在发布仅仅五天后,ChatGPT 就吸引了惊人的一百万用户,这在科技行业及其他领域引起了轰动。作为一个副作用,OpenAI API 用于人工智能文本生成的接口突然曝光,尽管它已经可用了三年。ChatGPT 界面展示了这

    2024年01月20日
    浏览(73)
  • 【自制视频课程】C++OpnecV基础35讲——第一章 前言

            首先,opencv是一个广泛使用的计算机视觉库,它提供了丰富的图像处理和计算机视觉算法,可以帮助我们快速地开发出高质量的图像处理应用程序;         其次,opencv是一个开源库,可以免费使用和修改,这为我们提供了一个学习和研究计算机视觉的良好平

    2024年02月05日
    浏览(55)
  • 【C#】Windows服务(Service)安装及启停

    目录 一、创作背景 二、问题解决 2.1 安装Windows service服务 2.2 主方法Main()主方法改写 2.3 安装service服务/卸载service服务 2.4 服务启停 2.5 服务调用运行 2.6 关于权限的提升 三、资源分享 3.1 引入组件 3.2 新手使用 我能抽象出整个世界,但是我不能抽象你。 想让你成为私有常量,

    2023年04月08日
    浏览(40)
  • C#引用Web Service 类型方法,添加搜索本地服务器Web Service 接口调用方法

    wsdl首先保证现在网络能调用web service接口,右键项目添加服务引用 点击高级 添加web服务 输入搜索的服务器接口,选中你要添加调用的方法即可 添加完成调用方法

    2024年02月13日
    浏览(43)
  • C#使用Asp.Net创建Web Service接口并调用

    目录 一.创建Asp.net web应用以及Web Service服务 (1).运行环境 (2)创建项目 二.创建控制台应用来调用上面创建的Web Service 开发工具: Visual Studio 2022 Current (免费社区版) 框架版本: .net framework4.7.2,更高的.net 5 、net6貌似没有默认提供带web service的asp.net 应用模板了。 确保VS的工作负荷有

    2024年01月18日
    浏览(44)
  • 毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前言

    资源链接:https://download.csdn.net/download/m0_46573428/87796553 前言:毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前言_人工智能技术小白修炼手册的博客-CSDN博客 首页与导航:毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前端:首页与导航栏_人工智

    2024年02月14日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包