加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

一文说通Dotnet Core的后台任务

发布时间:2021-05-21 07:54:44 所属栏目:大数据 来源: https://www.jb51.cc
导读:这是一文说通系列的第二篇,里面有些内容会用到第一篇中间件的部分概念。如果需要,可以参看第一篇:一文说通Dotnet Core的中间件 ? 一、前言 后台任务在一些特殊的应用场合,有相当的需求。 比方,我们需要实现一个定时任务、或周期性的任务、或非API输出的

微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

这是一文说通系列的第二篇,里面有些内容会用到第一篇中间件的部分概念。如果需要,可以参看第一篇:一文说通Dotnet Core的中间件

?

一、前言

后台任务在一些特殊的应用场合,有相当的需求。

比方,我们需要实现一个定时任务、或周期性的任务、或非API输出的业务响应、或不允许并发的业务处理,像提现、支付回调等,都需要用到后台任务。

?

通常,我们在实现后台任务时,有两种选择:WebAPI和Console。

下面,我们会用实际的代码,来理清这两种工程模式下,后台任务的开发方式。

????为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13081020.html

二、开发环境&基础工程

这个Demo的开发环境是:Mac + VS Code + Dotnet Core 3.1.2。

$?dotnet?--info
.NET?Core?SDK?(reflecting?any?global.json):
?Version:???3.1.201
?Commit:????b1768b4ae7

Runtime?Environment:
?OS?Name:?????Mac?OS?X
?OS?Version:??10.15
?OS?Platform:?Darwin
?RID:?????????osx.10.15-x64
?Base?Path:???/usr/local/share/dotnet/sdk/3.1.201/

Host?(useful?for?support):
??Version:?3.1.3
??Commit:??4a9f85e9f8

.NET?Core?SDKs?installed:
??3.1.201?[/usr/local/share/dotnet/sdk]

.NET?Core?runtimes?installed:
??Microsoft.AspNetCore.App?3.1.3?[/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
??Microsoft.NETCore.App?3.1.3?[/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

?

首先,在这个环境下建立工程:

  1. 创建Solution
%?dotnet?new?sln?-o?demo
The?template?"Solution?File"?was?created?successfully.
  1. 这次,我们用Webapi创建工程
%?cd?demo
%?dotnet?new?webapi?-o?webapidemo
The?template?"ASP.NET?Core?Web?API"?was?created?successfully.

Processing?post-creation?actions...
Running?'dotnet?restore'?on?webapidemo/webapidemo.csproj...
??Restore?completed?in?179.13?ms?for?demo/demo.csproj.

Restore?succeeded.
%?dotnet?new?console?-o?consoledemo
The?template?"Console?Application"?was?created?successfully.

Processing?post-creation?actions...
Running?'dotnet?restore'?on?consoledemo/consoledemo.csproj...
??Determining?projects?to?restore...
??Restored?consoledemo/consoledemo.csproj?(in?143?ms).

Restore?succeeded.
  1. 把工程加到Solution中
%?dotnet?sln?add?webapidemo/webapidemo.csproj
%?dotnet?sln?add?consoledemo/consoledemo.csproj

基础工程搭建完成。

三、在WebAPI下实现一个后台任务

WebAPI下后台任务需要作为托管服务来实现,而托管服务,需要实现IHostedService接口。

?

首先,我们需要引入一个库:

cd?webapidemo
%?dotnet?add?package?Microsoft.Extensions.Hosting

引入后,我们就有了IHostedService

?

下面,我们来做一个IHostedService的派生托管类:

namespace?webapidemo
{
????public?class?DemoService?:?IHostedService
????{
????????public?DemoService()
????????
{
????????}

????????public?Task?StartAsync(CancellationToken?cancellationToken)
????????{
????????????throw?new?NotImplementedException();
????????}

????????StopAsyncnew?NotImplementedException();
????????}
????}
}

IHostedService需要实现两个方法:StartAsyncStopAsync。其中:

StartAsync: 用于启动后台任务;

StopAsync:主机Host正常关闭时触发。

?

如果派生类中有任何非托管资源,那还可以引入IDisposable,并通过实现Dispose来清理非托管资源。

?

这个类生成后,我们将这个类注入到ConfigureServices中,以使这个类在Startup.Configure调用之前被调用:

public?void?ConfigureServices(IServiceCollection?services)
{
????services.AddControllers();

????services.AddHostedService<DemoService>();
}

下面,我们用一个定时器的后台任务,来加深理解:

TimerService?:?IHostedService,?IDisposable
????{
??????????/*?下面这两个参数是演示需要,非必须?*/
????????private?readonly?ILogger?_logger;
????????private?int?executionCount?=?0;

??????????/*?这个是定时器?*/
????????private?Timer?_timer;

????????TimerService(ILogger<TimerService>?logger)
????????{
????????????_logger?=?logger;
????????}

????????Dispose()
????????{
????????????_timer?.Dispose();

????????}

????????DoWork(object?state)
????????{
????????????var?count?=?Interlocked.Increment(ref?executionCount);

????????????_logger.LogInformation($"Service?proccessing?{count}");
????????}

????????(CancellationToken?cancellationToken)
????????{
????????????_logger.LogInformation("Service?starting");

????????????_timer?=?new?Timer(DoWork,?null,?TimeSpan.Zero,?TimeSpan.FromSeconds(5));
????????????return?Task.CompletedTask;
????????}

????????"Service?stopping");

????????????_timer?.Change(Timeout.Infinite,?0);
????????????return?Task.CompletedTask;
????????}
????}
}

注入到ConfigureServices中:

(IServiceCollection?services)
{
????services.AddControllers();

????services.AddHostedService<TimerService>();
}

就OK了。代码比较简单,就不解释了。

四、WebAPI后台任务的依赖注入变形

上一节的示例,是一个简单的形态。

下面,我们按照标准的依赖注入,实现一下这个定时器。

?

依赖注入的简单样式,请参见一文说通Dotnet Core的中间件。

?

首先,我们创建一个接口IWorkService

public?interface?IWorkService
????{
????????Task?();
????}
}

再根据IWorkService,建立一个实体类:

WorkService?:?IWorkService
????{
????????private?Timer?_timer;
????????0;

????????WorkService(ILogger<WorkService>?logger)
????????{
????????????_logger?=?logger;
????????}

????????public?async?Task?()
????????{
????????????var?count?=?Interlocked.Increment(ref?executionCount);

????????????_logger.LogInformation($"Service?proccessing?{count}");
????????}
????}
}

这样就建好了依赖的全部内容。

?

下面,创建托管类:

HostedService?:?IHostedService,?IDisposable
????{
????????private?readonly?ILogger<HostedService>?_logger;
????????public?IServiceProvider?Services?{?get;?}
????????HostedService(IServiceProvider?services,?ILogger<HostedService>?logger)
????????{
????????????Services?=?services;
????????????_logger?=?logger;
????????}

??????????()
????????{
????????????_timer?.Dispose();
????????}

????????(object?state)
????????{
????????????_logger.LogInformation("Service?working");

????????????using?(var?scope?=?Services.CreateScope())
????????????{
????????????????var?scopedProcessingService?=
????????????????????scope.ServiceProvider
????????????????????????.GetrequiredService<IWorkService>();

????????????????scopedProcessingService.DoWork().GetAwaiter().GetResult();
????????????}
????????}

????????return?Task.CompletedTask;
????????}


????????return?Task.CompletedTask;
????????}
????}
}

把托管类注入到(IServiceCollection?services)
{
????services.AddControllers();

????services.AddHostedService<HostedService>();
????services.AddSingleton<IWorkService,?WorkService>();
}

这样就完成了。

?

这种模式下,可以根据注入的内容切换应用的执行内容。不过,这种模式需要注意services.AddSingletonservices.AddScopedservices.AddTransient的区别。

五、Console下的后台任务

Console应用本身就是后台运行,所以区别于WebAPI,它不需要托管运行,也不需要Microsoft.Extensions.Hosting库。

我们要做的,就是让程序运行,就OK。

?

下面是一个简单的Console模板:

namespace?consoledemo
{
????Program
????{

????????static?AutoResetEvent?_exitEvent;

????????static?async?Task?Main(string[]?args)
????????{
????????????????/*?确保程序只有一个实例在运行?*/
????????????bool?isRuned;
????????????Mutex?mutex?=?new?Mutex(true,?"OnlyRunOneInstance",?out?isRuned);
????????????if?(!isRuned)
????????????????return;

????????????await?();

????????????????????????/*?后台等待?*/
????????????_exitEvent?=?new?AutoResetEvent(false);
????????????_exitEvent.WaitOne();
????????}

????????()
????????{
????????????new?NotImplementedException();
????????}
????}
}

这个模板有两个关键的内容:

  1. 单实例运行:通常后台任务,只需要有一个实例运行。所以,第一个小段,是解决单实例运行的。多次启动时,除了第一个实例外,其它的实例会自动退出;
  2. 后台等待:看过很多人写的,在这儿做后台等待时,用了一个无限的循环。类似于下面的:
while(true)
{
????Thread.Sleep(1000);
}

这种方式也没什么太大的问题。不过,这段代码总是要消耗cpu的计算量,虽然很少,但做为后台任务,或者说Service,毕竟是一种消耗,而且看着不够高大上。

?

当然如果我们需要中断,我们也可以把这个模板改成这样:

string[]?args)
????????{
????????????return;

????????????_exitEvent?=?false);
????????????(_exitEvent);
????????????_exitEvent.WaitOne();
????????}

????????(AutoResetEvent?_exitEvent)
????????{
????????????/*?Your?Code?Here?*/

????????????_exitEvent.Set();
????????}
????}
}

这样就可以根据需要,来实现中断程序并退出。

六、Console应用的其它运行方式

上一节介绍的Console,其实是一个应用程序。

在实际应用中,Console程序跑在Linux服务器上,我们可能会有一些其它的要求:

  1. 定时运行

Linux上有一个Service,叫cron,是一个用来定时执行程序的服务。

这个服务的设定,需要另一个命令:crontab,位置在/usr/bin下。

具体命令格式这儿不做解释,网上随便查。

  1. 运行到后台

命令后边加个&字符即可:

$?./command?&
  1. 运行为Service

需要持续运行的应用,如果以Console的形态存在,则设置为Service是最好的方式。

Linux下,设置一个应用为Service很简单,就这么简单三步:

第一步:在/etc/systemd/system下面,创建一个service文件,例如command.service

[Unit]
#?Service的描述,随便写
Description=Command

[Service]
RestartSec=2s
Type=simple
#?执行应用的默认用户。应用如果没有特殊要求,最好别用root运行
User=your_user_name
Group=your_group_name
#?应用的目录,绝对路径
WorkingDirectory=your_app_folder
#?应用的启动路径
ExecStart=your_app_folder/your_app
Restart=always

[Install]
WantedBy=multi-user.target

差不多就这么个格式。参数的详细说明可以去网上查,实际除了设置,就是运行了一个脚本。

第二步:把这个command.service加上运行权限:

#?chmod?+x?./command.service

第三步:注册为Service:

#?systemctl?enable?command.service

完成。

为了配合应用,还需要记住两个命令:启动和关闭Service

#?#启动Service
#?systemctl?start?command.service
#?#关闭Service
#?systemctl?stop?command.service

七、写在后边的话

今天这个文章,是因为前两天,一个兄弟跑过来问我关于数据总线的实现方式,而想到的一个点。

?

很多时候,大家在写代码的时候,会有一种固有的思想:写WebAPI,就想在这个框架中把所有的内容都实现了。这其实不算是一个很好的想法。WebAPI,在业务层面,就应该只是实现简单的处理请求,返回结果的工作,而后台任务跟这个内容截然不同,通常它只做处理,不做返回 --- 事实上也不太好返回,要么客户端等待时间太长,要么客户端已经断掉了。换句话说,用WebAPI实现总线,绝不是一个好的方式。

不过,Console运行为Service,倒是一个总线应用的绝好方式。如果需要按序执行,可以配合MQ服务器,例如RabbitMQ,来实现消息的按序处理。

?

再说代码。很多需求,本来可以用很简单的方式实现。模式这个东西,用来面试,用来讲课,都是很好的内容,但实际开发中,如果有更简单更有效的方式,用起来!Coding的工作是实现,而不是秀技术。当然,能否找到简单有效的方式,这个可能跟实际的技术面有关系。但这并不是一个不能跨越的坎。

多看,多想,每天成长一点点!

?

今天的代码,在:https://github.com/humornif/Demo-Code/tree/master/0012/demo

(全文完)

?


?

    推荐文章
      热点阅读