netcore后台任务注意事项

开局一张图,故事慢慢编!这是一个后台任务打印时间的德莫,代码如下:
using BackGroundTask; var builder = WebApplication.CreateBuilder();
builder.Services.AddTransient<TickerService>();
builder.Services.AddHostedService<TickerBackGroundService>();
builder.Build().Run();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundTask
{
internal class TickerService
{
private event EventHandler<TickerEventArgs> Ticked;
public TickerService()
{
Ticked += OnEverySecond;
Ticked += OnEveryFiveSecond;
}
public void OnEverySecond(object? sender,TickerEventArgs args)
{
Console.WriteLine(args.Time.ToLongTimeString());
}
public void OnEveryFiveSecond(object? sender, TickerEventArgs args)
{
if(args.Time.Second %5==0)
Console.WriteLine(args.Time.ToLongTimeString());
}
public void OnTick(TimeOnly time)
{
Ticked?.Invoke(this, new TickerEventArgs(time));
}
}
internal class TickerEventArgs
{
public TimeOnly Time { get; }
public TickerEventArgs(TimeOnly time)
{
Time = time;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundTask
{
internal class TickerBackGroundService : BackgroundService
{
private readonly TickerService _tickerService;
public TickerBackGroundService(TickerService tickerService)
{
_tickerService = tickerService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_tickerService.OnTick(TimeOnly.FromDateTime(DateTime.Now));
await Task.Delay(1000,stoppingToken);
}
}
}
}
结果和预期一样,每秒打印一下时间,五秒的时候会重复一次。
代码微调,把打印事件改成打印guid,新增TransientService类:
internal class TransientService
{
public Guid Id { get; }=Guid.NewGuid();
}
微调后代码如下:
using BackGroundTask; var builder = WebApplication.CreateBuilder();
builder.Services.AddTransient<TickerService>();
builder.Services.AddTransient<TransientService>(); //新增生成guid类
builder.Services.AddHostedService<TickerBackGroundService>();
builder.Build().Run();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundTask
{
internal class TickerService
{
private event EventHandler<TickerEventArgs> Ticked;
private readonly TransientService _transientService; //注入TransientService
public TickerService(TransientService transientService)
{
Ticked += OnEverySecond;
Ticked += OnEveryFiveSecond;
_transientService = transientService; }
public void OnEverySecond(object? sender,TickerEventArgs args)
{
Console.WriteLine(_transientService.Id); //打印guid
}
public void OnEveryFiveSecond(object? sender, TickerEventArgs args)
{
if(args.Time.Second %5==0)
Console.WriteLine(args.Time.ToLongTimeString());
}
public void OnTick(TimeOnly time)
{
Ticked?.Invoke(this, new TickerEventArgs(time));
}
}
internal class TickerEventArgs
{
public TimeOnly Time { get; }
public TickerEventArgs(TimeOnly time)
{
Time = time;
}
}
}
TickerBackGroundService类没有做改动,来看看结果:

看似没问题,但是这个guid每次拿到的是一样的,再来看注入的TransientService类,是瞬时的,而且TickerService也是瞬时的。那应该每次会拿到新的对象新的guid才对。那这个后台任务是不是满足不了生命周期控制的要求呢?
问题就出在下面的代码上:
while (!stoppingToken.IsCancellationRequested)
{
_tickerService.OnTick(TimeOnly.FromDateTime(DateTime.Now));
await Task.Delay(1000,stoppingToken);
}
任务只要不停止,循环会一直下去,所以构造函数注入的类不会被释放,除非程序重启。那么怎么解决这个问题呢,那就是在while里面每次每次循环都创建一个新的对象。那就可以引入ServiceProvider对象。改造后的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleBackGround
{
internal class GlobalService
{
public static IServiceProvider ServiceProvider { get; set; }
}
}
using ConsoleBackGround; var builder = WebApplication.CreateBuilder(); builder.Services.AddTransient<TransientService>(); //Guid相同
//builder.Services.AddSingleton<TransientService>(); //构造函数使用Guid相同,使用scope对象注入不了,必须用ATransient
//builder.Services.AddScoped<TransientService>(); //构造函数使用Guid相同, 使用scope对象注入不了,必须用ATransient
builder.Services.AddTransient<TickerService>(); GlobalService.ServiceProvider = builder.Services.BuildServiceProvider(); //一定要在注入之后赋值,要不然只会拿到空对象。
builder.Services.AddHostedService<TickerBackGroundService>(); builder.Build().Run();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleBackGround
{
internal class TickerBackGroundService : BackgroundService
{
//private readonly TickerService _tickerService;
//public TickerBackGroundService(TickerService tickerService)
//{
// _tickerService = tickerService;
//}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//_tickerService.OnTick(TimeOnly.FromDateTime(DateTime.Now)); //guid不会变
using var scope = GlobalService.ServiceProvider.CreateScope();
var _tickerService = scope.ServiceProvider.GetService<TickerService>();
_tickerService?.OnTick(TimeOnly.FromDateTime(DateTime.Now)); //可以保证guid会变
await Task.Delay(1000,stoppingToken);
}
}
}
}
问题出在循环上所以TickerService代码不需要做任何更改。针对方便构造函数注入serviceprovider的情况完全不需要全局的GlobalService,通过构造函数注入的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleBackGround
{
internal class TickerBackGroundService : BackgroundService
{
private readonly IServiceProvider _sp;
public TickerBackGroundService(IServiceProvider sp)
{
_sp = sp;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
////_tickerService.OnTick(TimeOnly.FromDateTime(DateTime.Now)); //guid不会变
using var scope = _sp.CreateScope();
var _tickerService = scope.ServiceProvider.GetService<TickerService>();
_tickerService?.OnTick(TimeOnly.FromDateTime(DateTime.Now)); //可以保证guid会变
await Task.Delay(1000,stoppingToken);
}
}
}
}
运行结果符合预期:

下面看看使用MediatR的代码,也可以达到预期:
using BackGroundMediatR;
using MediatR; Console.Title = "BackGroundMediatR";
var builder = WebApplication.CreateBuilder();
//builder.Services.AddSingleton<TransientService>(); //打印相同的guid
builder.Services.AddTransient<TransientService>(); //打印不同的guid
builder.Services.AddMediatR(typeof(Program)); builder.Services.AddHostedService<TickerBackGroundService>(); builder.Build().Run();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundMediatR
{
internal class TransientService
{
public Guid Id { get; }=Guid.NewGuid();
}
}
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundMediatR
{
internal class TimedNotification:INotification
{
public TimeOnly Time { get; set; }
public TimedNotification(TimeOnly time)
{
Time = time;
}
}
}
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundMediatR
{
internal class EventSecondHandler : INotificationHandler<TimedNotification>
{
private readonly TransientService _service;
public EventSecondHandler(TransientService service)
{
_service = service;
}
public Task Handle(TimedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine(_service.Id);
return Task.CompletedTask;
}
}
}
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundMediatR
{
internal class EveryFiveSecondHandler : INotificationHandler<TimedNotification>
{
public Task Handle(TimedNotification notification, CancellationToken cancellationToken)
{
if(notification.Time.Second % 5==0)
Console.WriteLine(notification.Time.ToLongTimeString());
return Task.CompletedTask;
}
}
}
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BackGroundMediatR
{
internal class TickerBackGroundService : BackgroundService
{
private readonly IMediator _mediator;
public TickerBackGroundService(IMediator mediator)
{
_mediator = mediator;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var timeNow = TimeOnly.FromDateTime(DateTime.Now);
await _mediator.Publish(new TimedNotification(timeNow));
await Task.Delay(1000,stoppingToken);
}
}
}
}
执行结果如下:

代码链接:
exercise/Learn_Event at master · liuzhixin405/exercise (github.com)
Over!
netcore后台任务注意事项的更多相关文章
- 【5min+】后台任务的积木。.NetCore中的IHostedService
系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...
- .netcore开发环境和服务器注意事项
对于开发环境,如果你需要使用.netcore命令的话,你需要安装SDK:如果你还需要运行.netcore的网站的话,你必须还要安装它的[runtime]和[hosting server]: 对于服务器 ...
- Swagger/OpenAPI By Swashbuckle在NetCore 3.1中较NetCore 2.2使用的注意事项及入门
方案选择 使用Web API时,了解其各种方法对开发人员来说可能是一项挑战. Swagger也称为OpenAPI(Open Application Programming Interface,开放应用 ...
- 在.netcore webapi项目中使用后台任务工具Hangfire
安装Hangfire 在webapi项目中通过nuget安装Hangfire.Core,Hangfire.SqlServer,Hangfire.AspNetCore,截止到目前的最新版本是1.7.6. ...
- 开发Adobe AIR移动应用程序的考虑事项
http://www.adobe.com/cn/devnet/air/articles/considerations-air-apps-mobile.html Adobe AIR 经过发展演进,已经超 ...
- 与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载)
原文:与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载) [索引页][源码下载] 与众不同 windows phone (13) ...
- ubuntu16.04-x64系统中Jexus web server部署.NetCore和端口分析引发的猜想!
您有这样的牢骚么? 有一周没更新博客了,简单说下在干什么吧:主要是公司安排对接某旅游大公司的接口,接口数量倒也就10个左右,对接完后还需要加入到业务系统中和App端,因此还是需要花点时间的:时间上来说 ...
- .NetCore+Jexus代理+Redis模拟秒杀商品活动
开篇叙 本篇将和大家分享一下秒杀商品活动架构,采用的架构方案正如标题名称.NetCore+Jexus代理+Redis,由于精力有限所以这里只设计到商品添加,抢购,订单查询,处理队列抢购订单的功能:有不 ...
- CentOS利用Nginx+Docker部署.netcore应用
安装docker 官方文档https://docs.docker.com/engine/installation/linux/docker-ce/centos/ [root@sn ~]# yum re ...
随机推荐
- Solution -「多校联训」I Love Random
\(\mathcal{Description}\) 给定排列 \(\{p_n\}\),可以在其上进行若干次操作,每次选取 \([l,r]\),把其中所有元素变为原区间最小值,求能够得到的所有不同序 ...
- Solution -「CF 802C」Heidi and Library (hard)
\(\mathcal{Descriptoin}\) Link. 你有一个容量为 \(k\) 的空书架,现在共有 \(n\) 个请求,每个请求给定一本书 \(a_i\).如果你的书架里没有这本书 ...
- 使用注解实现SpringIOC和SpringAOP
使用注解实现ioc @Component:实现Bean组件的定义 @Repository:标注dao类 @Service:标注业务类 @Controller:标注控制类 Bean的自动装配: @Aut ...
- java宝典笔记(一)
第四章java基础知识 4.1基本概念 一.java优点 1.面向对象(封装.继承.多态) 2.可移植性.平台无关,一次编译,到处运行.Windows,Linux,macos等.java为解释性语言, ...
- Json:Java对象和Json文本转换工具类
Json 是一个用于 Java 对象 和 Json 文本 相互转换的工具类. 安装 下载源码 git clone https://github.com/njdi/durian.git 编译源码 cd ...
- RFC3918组播组容量测试——网络测试仪实操
一.简介 1.RFC3918简介 历史 · 在1999年3月成为正式标准 功能 · 评测网络互连设备或网络系统的性能 · 网络设备: 交换机,路由器- 内容 · 定义了一整套测试方法,为不同厂家的设备 ...
- github push时提示Username for 'https://github.com' 解决办法
问题 github push时在输入账号密码后仍提示:Username for 'https://github.com',需要进一步输入账号密码. 解决方案 注意这里的账号密码并不是github的登录 ...
- prometheus k8s服务发现
Prometheus的服务发现在解决什么问题? 被监控的目标(target)是整个监控体系中重要组成部分,传统监控系统zabbix通过 网络发现的机制自动创建主机到zabbix-server,进而快速 ...
- Linux:保证数据安全落盘
背景 在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer ...
- POJ3368题解
题目大意:一个非降序序列,有若干查询,每次查询一个区间中重复次数最多的数字的个数. 思路:因为是非降序的,所以可以从头遍历把每个相同的数字划为一个块,用p[i]表示ai划分到了哪个块里面,同时还可以记 ...