開局一張圖,故事慢慢編!這是一個後臺任務列印時間的德莫,程式碼如下:
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!