大佬教程收集整理的这篇文章主要介绍了谈谈.NET Core中基于Generic Host来实现后台任务,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
<div class="toc">
<p class="toc-title">目录
很多时候,后台任务对我们来说是一个利器,帮我们在后面处理了成千上万的事情。
在.NET Framework时代,我们可能比较多的就是一个项目,会有一到多个对应的Windows服务,这些Windows服务就可以当作是我们所说的后台任务了。
我喜欢将后台任务分为两大类,一类是不停的跑,好比MQ的消费者,RPC的服务端。另一类是定时的跑,好比定时任务。
那么在.NET Core时代是不是有一些不同的解决方案呢?答案是肯定的。
Generic Host就是其中一种方案,也是本文的主角。
Generic Host是ASP.NET Core 2.1中的新增功能,它的目的是将http管道从Web Host的API中分离出来,从而启用更多的Host方案。
这样可以让基于Generic Host的一些特性延用一些基础的功能。如:如配置、依赖关系注入和日志等。
Generic Host更倾向于通用性,换句话就是说,我们即可以在Web项目中使用,也可以在非Web项目中使用!
虽然有时候后台任务混杂在Web项目中并不是一个太好的选择,但也并不失是一个解决方案。尤其是在资源并不充足的时候。
比较好的做法还是让其独立出来,让它的职责更加单一。
下面就先来看看如何创建后台任务吧。
我们先来写两个后台任务(一个一直跑,一个定时跑),体验一下这些后台任务要怎么上手,同样也是我们后面要使用到的。
这两个任务统一继承BACkgroundservice这个抽象类,而不是IHostedservice这个接口。后面会说到两者的区别。
先上代码
public class PrinterHostedservice2 : BACkgroundservice { private readonly ILogger _logger; private readonly AppSetTings _setTings;public PrinterHostedservice2(ILoggerFactory loggerFactory,IOptionsSnapshot<AppSetTings> options) { this._logger = loggerFactory.CreateLogger<PrinterHostedservice2>(); this._setTings = options.Value; } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Printer2 is stopped"); return Task.CompletedTask; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationrequested) { _logger.LogInformation($"Printer2 is working. {_setTings.PrinterDelaySeconD}"); await Task.Delay(TimeSpan.FromSeconds(_setTings.PrinterDelaySecond),stoppingToken); } }
@H_450_62@}@H_450_62@
来看看里面的细节。
我们的这个服务继承了BACkgroundservice,就一定要实现里面的ExecuteAsync,至于StartAsync和StopAsync等方法可以选择性的override。
我们ExecuteAsync在里面就是输出了一下日志,然后休眠在配置文件中指定的秒数。
这个任务可以说是最简单的例子了,其中还用到了依赖注入,如果想在任务中注入数据仓储之类的,应该就不需要再多说了。
同样的方式再写一个定时的。
- 定时跑的后台任务
这里借助了Timer来完成定时跑的功能,同样的还可以结合Quartz来完成。
public class TimerHostedservice : BACkgroundservice { //other ...private Timer _timer; protected override Task ExecuteAsync(CancellationToken stoppingToken) { _timer = new Timer(DoWork,null,TimeSpan.Zero,TimeSpan.FromSeconds(_setTings.TimerPeriod)); return Task.CompletedTask; } private void DoWork(object statE) { _logger.LogInformation("Timer is working"); } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timer is stopping"); _timer?.Change(Timeout.Infinite,0); return base.StopAsync(cancellationToken); } public override void Dispose() { _timer?.Dispose(); base.Dispose(); }
@H_450_62@}@H_450_62@
和第一个后台任务相比,没有太大的差异。
下面我们先来看看如何用控制台的形式来启动这两个任务。
这里会同时引入NLog来记录任务跑的日志,方便我们观察。
@H_233_0@main函数的代码如下:class Program { static async Task Main(String[] args) { var builder = new HostBuilder() //logging .ConfigureLogging(factory => { //use nlog factory.AddNLog(new NLogProviderOptions { CapturemessageTemplates = true,CapturemessageProperties = true }); NLog.LogManager.LoadConfiguration("nlog.config"); }) //host config .ConfigureHostConfiguration(config => { //command line if (args != null) { config.AddCommandLine(args); } }) //app config .ConfigureAppConfiguration((hostContext,config) => { var env = hostContext.HosTingEnvironment; config.AddJsonFile("appsetTings.json",optional: true,reloadOnChange: truE) .AddJsonFile($"appsetTings.{env.EnvironmentNamE}.json",reloadOnChange: truE);config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) //service .Configureservices((hostContext,services) => { services.Addoptions(); services.Configure<AppSetTings>(hostContext.Configuration.GetSection("AppSetTings")); //basic usage services.AddHostedservice<PrinterHostedservice2>(); services.AddHostedservice<TimerHostedservice>(); }) ; //console await builder.RunConsoleAsync(); ////start and wait for shutdown //var host = builder.build(); //using (host) //{ // await host.StartAsync(); // await host.WaitForShutdownAsync(); //} }
@H_450_62@}@H_450_62@
对于控制台的方式,需要我们对HostBuilder有一定的了解,虽说它和WebHostBuild有相似的地方。可能大部分时候,我们是直接使用了
WebHost.CreateDefaultBuilder(args)
来构造的,如果对CreateDefaultBuilder里面的内容没有了解,那么对上面的代码可能就不会太清晰。上述代码的大致流程如下:
- new一个HostBuilder对象
- 配置日志,主要是接入了NLog
- Host的配置,这里主要是引入了CommandLine,因为需要传递参数给程序
- 应用的配置,指定了配置文件,和引入CommandLine
- service的配置,这个就和我们在Startup里面写的差不多了,最主要的是我们的后台服务要在这里注入
- 启动
其中,
2-5的顺序可以按个人习惯来写,里面的内容也和我们写Startup大同小异。
第6步,启动的时候,有多种方式,这里列出了两种行为等价的方式。
a. 通过RunConsoleAsync的方式来启动
b. 先StartAsync然后再WaitForShutdownAsync
RunConsoleAsync的奥秘,我觉得还是直接看下面的代码比较容易懂。
////// Listens for Ctrl+C or SIGTERM and calls /// to configure. ///s. /// This will unblock extensions like RunAsync and WaitForShutdownAsync. /// The same instance of the public static IHostBuilder UseConsoleLifetime(this IHostBuilder hostBuilder) { return hostBuilder.Configureservices((context,collection) => collection.AddSingleton ()); } ///
/// Enables console support,builds and starts the host,and waits for Ctrl+C or SIGTERM to shut down.
///
/// <param name="hostBuilder">Theto configure.
/// <param name="cancellationToken">
///
public static Task RunConsoleAsync(this IHostBuilder hostBuilder,CancellationToken cancellationToken = default)
{
return hostBuilder.UseConsoleLifetime().build().RunAsync(cancellationToken);
}@H_450_62@这里涉及到了一个比较重要的IHostLifetime,Host的生命周期,ConsoleLifeTime是默认的一个,可以理解成当接收到ctrl+c这样的指令时,它就会触发停止。
接下来,写一下nlog的配置文件
@H_450_62@ @H_156_197@ 这个时候已经可以通过命令启动@R_598_9@R_944_11241@@了。
dotnet run -- --environment Staging@H_450_62@这里指定了运行环境为Staging,而不是默认的Production。
这个时候大致效果如下:
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/6af2e9be597f3e04bc2b0eea51919b60.png">
虽然效果已经出来了,不过大家可能会觉得这个有点小打小闹,下面来个略微复杂一点的后台任务,用来监听并消费RabbitMQ的消息。
public class ComsumeRabbitMQHostedservice : BACkgroundservice { private readonly ILogger _logger; private readonly AppSetTings _setTings; private IConnection _connection; private IModel _chAnnel;public ComsumeRabbitMQHostedservice(ILoggerFactory loggerFactory,IOptionsSnapshot<AppSetTings> options) { this._logger = loggerFactory.CreateLogger<ComsumeRabbitMQHostedservice>(); this._setTings = options.Value; InitRabbitMQ(this._setTings); } private void InitRabbitMQ(AppSetTings setTings) { var factory = new ConnectionFactory { HostName = setTings.HostName,}; _connection = factory.CreateConnection(); _chAnnel = _connection.CreateModel(); _chAnnel.ExchangeDeclare(_setTings.Exchangename,ExchangeType.Topic); _chAnnel.QueueDeclare(_setTings.Queuename,false,null); _chAnnel.QueueBind(_setTings.Queuename,_setTings.Exchangename,_setTings.RoutIngKey,null); _chAnnel.basicQos(0,1,falsE); _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown; } protected override Task ExecuteAsync(CancellationToken stoppingToken) { stoppingToken.ThrowIfCancellationrequested(); var consumer = new EvenTingBasicConsumer(_chAnnel); consumer.Received += (ch,ea) => { var content = System.Text.Encoding.UTF8.GetString(ea.body); Handlemessage(content); _chAnnel.basicAck(ea.DeliveryTag,falsE); }; consumer.Shutdown += OnConsumerShutdown; consumer.Registered += OnConsumerRegistered; consumer.Unregistered += OnConsumerUnregistered; consumer.ConsumerCancelled += OnConsumerConsumerCancelled; _chAnnel.basicConsume(_setTings.Queuename,consumer); return Task.CompletedTask; } private void Handlemessage(String content) { _logger.LogInformation($"consumer received {Content}"); } private void OnConsumerConsumerCancelled(object sender,ConsumerEventArgs E) { ... } private void OnConsumerUnregistered(object sender,ConsumerEventArgs E) { ... } private void OnConsumerRegistered(object sender,ConsumerEventArgs E) { ... } private void OnConsumerShutdown(object sender,ShutdowneventArgs E) { ... } private void RabbitMQ_ConnectionShutdown(object sender,ShutdowneventArgs E) { ... } public override void Dispose() { _chAnnel.Close(); _connection.Close(); base.Dispose(); }
@H_450_62@}@H_450_62@
代码细节就不需要多说了,下面就启动MQ发送程序来模拟消息的发送
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/7099c8dc661da527c742cb61304ddb96.png">
同时看我们任务的日志输出
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/9049b58000084b401b170f74f075212b.png">
由启动到停止,效果都是符合我们预期的。
下面再来看看Web形式的后台任务是怎么处理的。
这种模式下的后台任务,其实就是十分简单的了。
我们只要在startup的Configureservices方法里面注册我们的几个后台任务就可以了。
public void Configureservices(IserviceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHostedservice(); services.AddHostedservice (); services.AddHostedservice (); }@H_450_62@ 启动Web站点后,我们发了20条MQ消息,再访问了一下Web站点的首页,最后是停止站点。
下面是日志结果,都是符合我们的预期。
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/4381e30ffa783c79f64dc07b1aa452d8.png">
可能大家会比较好奇,这三个后台任务是怎么混合在Web项目里面启动的。
答案就在下面的两个链接里。
上面说了那么多,都是在本地直接运行的,可能大家会比较关注这个要怎样部署,下面我们就不看看怎么部署。
部署的话,针对不同的情形(web和非web)都有不同的选择。
正常来说,如果本身就是web程序,那么平时我们怎么部署的,就和平时那样部署即可。
花点时间讲讲部署非web的情形。
其实这里的部署等价于让程序在后台运行。
在Linux下面让程序在后台运行方式有好多好多,Supervisor、Screen、pm2、systemctl等。
这里主要介绍一下systemctl,同时用上面的例子来进行部署,由于个人服务器没有MQ环境,所以没有启用消费MQ的后台任务。
先创建一个 service 文件
Hell">vim /etc/systemd/system/ghostdemo.service
@H_450_62@内容如下:
Hell">[Unit] Description=Generic Host Demo
@H_450_62@[service]
WorkingDirectory=/var/www/ghost
ExecStart=/usr/bin/dotnet /var/www/ghost/ConsoleGHost.dll --environment Staging
KillSignal=SIGINT
SyslogIdentifier=ghost-example[Install]
WantedBy=multi-user.target其中,各项配置的含义可以自行查找,这里不作说明。
然后可以通过下面的命令来启动和停止这个服务
Hell">service ghostdemo start service ghostdemo stop
@H_450_62@测试无误之后,就可以设为自启动了。
Hell">systemctl enable ghostdemo.service
@H_450_62@下面来看看运行的效果
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/7682dcdfba50dafb6ea65f7ce0ef47ae.png">
我们先启动服务,然后去查看实时日志,可以看到应用的日志不停的输出。
当我们停了服务,再看实时日志,@R_631_10585@我们的两个后台任务已经停止了,也没有日志再进来了。
再去看看服务系统日志
Hell">sudo journalctl -fu ghostdemo.service
@H_450_62@title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/db812c5f517e201a1c8d6f33b8e8b309.png">
发现它确实也是停了。
在这里,我们还可以看到服务的当前环境和根路径。
service和BACkgroundservice的区别">IHostedservice和BACkgroundservice的区别
前面的所有示例中,我们用的都是BACkgroundservice,而不是IHostedservice。
这两者有什么区别呢?
可以这样简单的理解,IHostedservice是原料,BACkgroundservice是一个用原料加工过一部分的半成品。
这两个都是不能直接当成成品来用的,都需要进行加工才能做成一个可用的成品。
同时也意味着,如果使用IHostedservice可能会需要做比较多的控制。
基于前面的打印后台任务,在这里使用IHostedservice来实现。
如果我们只是纯綷的把实现代码放到StartAsync方法中,那么可能就会有惊喜了。
public class PrinterHostedservice : IHostedservice,IDisposable { //other ....public async Task StartAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationrequested) { Console.WriteLine("Printer is working."); await Task.Delay(TimeSpan.FromSeconds(_setTings.PrinterDelaySecond),cancellationToken); } } public Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Printer is stopped"); return Task.CompletedTask; }
@H_450_62@} @H_450_62@
运行之后,想用ctrl+c来停止,发现还是一直在跑。
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/3a9fe6518dc709f61d8bff9ba4ac6b8f.png">
ps一看,这个进程还在,kill掉之后才不会继续输出。。
title="谈谈.NET Core中基于Generic Host来实现后台任务" alt="谈谈.NET Core中基于Generic Host来实现后台任务" src="https://cn.js-code.com/res/2019/02-08/23/1e1e56b1f9ed44500318a972ee1ad7fb.png">
问题出在那里呢?原因其实还是比较明显的,因为这个任务@R_940_9036@成功,一直处于启动中的状态!
换句话说,StartAsync方法还没有执行完。这个问题一定要小心再小心。
要怎么处理这个问题呢?解决方法也比较简单,可以通过引用一个变量来记录要运行的任务,将其从StartAsync方法中解放出来。
public class PrinterHostedservice3 : IHostedservice,IDisposable { //others ..... private bool _stopping; private Task _BACkgroundTask;public Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("Printer3 is starTing."); _BACkgroundTask = BACkgroundTask(cancellationToken); return Task.CompletedTask; } private async Task BACkgroundTask(CancellationToken cancellationToken) { while (!_stopping) { await Task.Delay(TimeSpan.FromSeconds(_setTings.PrinterDelaySecond),cancellationToken); Console.WriteLine("Printer3 is doing BACkground work."); } } public Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Printer3 is stopping."); _stopping = true; return Task.CompletedTask; } public void Dispose() { Console.WriteLine("Printer3 is disposing."); }
@H_450_62@}@H_450_62@
这样就能让这个任务真正的启动成功了!效果就不放图了。
相对来说,BACkgroundservice用起来会比较简单,实现核心的ExecuteAsync这个抽象方法就差不多了,出错的概率也会比较低。
在注册服务的时候,我们还可以通过编写IHostBuilder的扩展方法来完成。
public static class Extensions { public static IHostBuilder UseHostedservice(this IHostBuilder hostBuilder) where T : class,IHostedservice,IDisposable { return hostBuilder.Configureservices(services => services.AddHostedservice ()); } public static IHostBuilder UseComsumeRabbitMQ(this IHostBuilder hostBuilder) { return hostBuilder.Configureservices(services => services.AddHostedservice<ComsumeRabbitMQHostedservice>()); }
@H_450_62@}@H_450_62@
使用的时候就可以像下面一样。
var builder = new HostBuilder() //others ... .Configureservices((hostContext,services) => { services.Addoptions(); services.Configure(hostContext.Configuration.GetSection("AppSetTings")); //basic usage //services.AddHostedservice<PrinterHostedservice2>(); //services.AddHostedservice<TimerHostedservice>(); //services.AddHostedservice<ComsumeRabbitMQHostedservice>(); }) //extensions usage .UseComsumeRabbitMQ() .UseHostedservice<TimerHostedservice>() .UseHostedservice<PrinterHostedservice2>() //.UseHostedservice<ComsumeRabbitMQHostedservice>() ;</code></pre>
@H_450_62@<h2 id="总结">总结
Generic Host让我们可以用熟悉的方式来处理后台任务,不得不说这是一个很
大佬总结
以上是大佬教程为你收集整理的谈谈.NET Core中基于Generic Host来实现后台任务全部内容,希望文章能够帮你解决谈谈.NET Core中基于Generic Host来实现后台任务所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。