netcore后台任务注意事项
2023-04-18 12:57:11 时间
开局一张图,故事慢慢编!这是一个后台任务打印时间的德莫,代码如下:
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!
相关文章
- 【技术种草】cdn+轻量服务器+hugo=让博客“云原生”一下
- CLB运维&运营最佳实践 ---访问日志大洞察
- vnc方式登陆服务器
- 轻松学排序算法:眼睛直观感受几种常用排序算法
- 十二个经典的大数据项目
- 为什么使用 CDN 内容分发网络?
- 大数据——大数据默认端口号列表
- Weld 1.1.5.Final,JSR-299 的框架
- JavaFX 2012:彻底开源
- 提升as3程序性能的十大要点
- 通过凸面几何学进行独立于边际的在线多类学习
- 利用行动影响的规律性和部分已知的模型进行离线强化学习
- ModelLight:基于模型的交通信号控制的元强化学习
- 浅谈Visual Source Safe项目分支
- 基于先验知识的递归卡尔曼滤波的代理人联合状态和输入估计
- 结合网络结构和非线性恢复来提高声誉评估的性能
- 最佳实践丨云开发CloudBase多环境管理实践
- TimeVAE:用于生成多变量时间序列的变异自动编码器
- 具有线性阈值激活的神经网络:结构和算法
- 内网渗透之横向移动 -- 从域外向域内进行密码喷洒攻击