netcore后台任务注意事项

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 EventHandler 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(); 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 EventHandler 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类没有做改动,来看看结果:
netcore后台任务注意事项
文章图片

看似没问题,但是这个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); } } } }

运行结果符合预期:
netcore后台任务注意事项
文章图片


下面看看使用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); } } } }

执行结果如下:
netcore后台任务注意事项
文章图片


代码链接:
exercise/Learn_Event at master · liuzhixin405/exercise (github.com)
Over!

    推荐阅读