• 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!

     

  • 相关阅读:
    数字档案一体化解决方案
    Python技法:实用运维脚本编写(进程/文件/目录操作)
    深度学习(3)---PyTorch中的张量
    应届毕业生谈毕业的故事
    如何测试 esp-matter_example_light 例程
    通信原理技术复习重点知识
    Java(三)(static,代码块,单例设计模式,继承)
    软考中级(软件设计师)——计算机网络(5分)与信息安全(3分)
    pytest Allure报告生成全量配置git+jenkins+allure
    使用 .NET Reactor 混淆C#程序后,调用ToJson()出现Newtonsoft.Json.JsonSerializationException
  • 原文地址:https://www.cnblogs.com/morec/p/16062509.html