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

  • 相关阅读:
    CSU oj 2092-Space Golf
    (全国多校重现赛一)F-Senior Pan
    URAL 1152 False Mirrors
    Codeforces D
    URAL 1635 Mnemonics and Palindromes
    URAL
    Codeforces 912E
    Codeforces 911E
    Codeforces 294D
    Codeforces 448E
  • 原文地址:https://www.cnblogs.com/morec/p/16062509.html
Copyright © 2020-2023  润新知