4

netcore后台任务注意事项 - 星仔007

 2 years ago
source link: https://www.cnblogs.com/morec/p/16062509.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK