netcore后台任务注意事项
文章图片
开局一张图,故事慢慢编!这是一个后台任务打印时间的德莫,代码如下:
using BackGroundTask; var builder = WebApplication.CreateBuilder(); builder.Services.AddTransient(); builder.Services.AddHostedService (); 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 EventHandlerTicked; 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(); builder.Services.AddTransient (); //新增生成guid类 builder.Services.AddHostedService (); 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 EventHandlerTicked; 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(); //Guid相同 //builder.Services.AddSingleton (); //构造函数使用Guid相同,使用scope对象注入不了,必须用ATransient //builder.Services.AddScoped (); //构造函数使用Guid相同, 使用scope对象注入不了,必须用ATransient builder.Services.AddTransient (); GlobalService.ServiceProvider = builder.Services.BuildServiceProvider(); //一定要在注入之后赋值,要不然只会拿到空对象。 builder.Services.AddHostedService (); 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?.OnTick(TimeOnly.FromDateTime(DateTime.Now)); //可以保证guid会变 await Task.Delay(1000,stoppingToken); } } } }
【netcore后台任务注意事项】问题出在循环上所以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?.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(); //打印相同的guid builder.Services.AddTransient (); //打印不同的guid builder.Services.AddMediatR(typeof(Program)); builder.Services.AddHostedService (); 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{ private readonly TransientService _service; public EventSecondHandler(TransientServiceservice) { _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{ 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!
推荐阅读
- 青龙教程资源分享|青龙快手极速版及定时任务时间解析
- FreeRTOS|FreeRTOS记录(三、RTOS任务调度原理解析_Systick、PendSV、SVC)
- 玩转SpringBoot之定时任务@Scheduled线程池配置
- GitHub|GitHub克隆项目没数据,后台管理权限项目等,解决方法
- SOTA效果+一键预测,PaddleNLP带你玩转11类NLP任务
- 操作系统|后台开发应该读的书
- Java如何实现定时任务()
- 项目实战!接入分布式定时任务框架
- Java基础案例教程 第六章集合类———【任务6-2】模拟新浪微博用户注册 (HashSet集合)
- Android|后台启动 Activity