NetCore HostService

In ASP.NET Core, background tasks can be implemented as hosted services, A hosted service is a class with background task logic that implements the IHostedService interface.  This article provies three hosted service examples:

  • Background task that runs on a timer.
  • Hosted service that activates as scoped service. The scoped service can use Dependency injection(DI),
  • Queued background tasks that run sequentially.

IHostedService interface
  The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellatlionToken)

              StartAsync contains the logic to start the background task. StartAsync is called before:

                 The app's request processing pipeline is configured.

                 The Server is started and IApplicationLifetime.ApplicationStarted is triggered.

        The StartAsync should be limited to short running tasks because hosted services are run sequentially, and no further services are started until StartAsync runs to completion.

  • StopAsync(CancellationToken)

           StopAsync is triggered when teh host is performing a graceful shutdown. StopAsync contains the logic to end the background task, implemnet IDisposable and finalizers to dispose of any unmannaged resources.

The cancellation token has a default 30 second timeout to indicate that the shutdown process should no longer be graceful. When cancellation is requested on the token:

  • Any remaining background operations that the app is performing should be aborted.
  • Any methods called in StopAsync should return promptly.

However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Therefore, any methods called or operations conducted in StopAsync might not occur.

To extend the default 30 second shutdown timeout, set:

The hosted service is activated once at app startup and gracefully shut down at app shutdown. If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Timed backgound tasks

复制代码
    public class TimedHostedService: IHostedService, IDisposable
    {
        private int excutionCount = 0;
        private readonly ILogger<TimedHostedService> _logger;
        private Timer? _timer = null;

        public TimedHostedService(ILogger<TimedHostedService> logger)
        {
            this._logger = logger;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Timed Hosted Service running..");
            for (int i = 0; i < 5; i++)
            {
                Task.Run(() =>
                {
                    _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
                });
            }           
            return Task.CompletedTask;
        }

        private void DoWork(object? state)
        {
            var count = Interlocked.Increment(ref excutionCount);
            _logger.LogInformation($"Timed Hosted Service is working. count:{count}.");
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Timed Hosted Service is stopping.");
            _timer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _logger.LogInformation($"Dispose timer object.");
            _timer?.Dispose();
        }
    }
Timed Host Service
复制代码

The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount concurrently.

Consuming a scoped service in a background task

复制代码
public class ConsumeScopedServiceHostedService : BackgroundService
    {
        private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

        public IServiceProvider _serviceProvider { get; }

        public ConsumeScopedServiceHostedService(IServiceProvider serviceProvider,
            ILogger<ConsumeScopedServiceHostedService> logger)
        {
            _serviceProvider = serviceProvider;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Consume Scoped Service Hosted Service running.");
            await DoWork(stoppingToken);
        }

        private async Task DoWork(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Consume Scoped Service Hosted Service is working.");
            using (var scope = _serviceProvider.CreateScope())
            {
                var scopedProcessingService = scope.ServiceProvider.GetService<IScopeProcessingService>();
                await scopedProcessingService.DoWork(stoppingToken);
            }
        }

        public override async Task StopAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Consume Scope Service Hosted Service is stopping.");
            await base.StopAsync(stoppingToken);
        }
    }
Consume Scope Service Hosted Service
复制代码
复制代码
    internal interface IScopeProcessingService
    {
        Task DoWork(CancellationToken stoppingToken);
    }

    internal class ScopedProcessingService : IScopeProcessingService
    {
        private int excutionCount = 0;
        private readonly ILogger _logger;
        
        public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
        {
            _logger = logger;
        }

        public async Task DoWork(CancellationToken stoppingToken)
        {
            while(!stoppingToken.IsCancellationRequested)
            {
                excutionCount++;
                _logger.LogInformation($"Scopped Processing Service is working. count: {excutionCount}");
                await Task.Delay(10000,stoppingToken);
            }
        }
    }
Scoped Process Service
复制代码

Queued background tasks

复制代码
    public interface IBackgroundTaskQueue
    {
        ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

        ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(CancellationToken cancellationToken);
    }

    public class BackgroundTaskQueue : IBackgroundTaskQueue
    {
        private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

        public BackgroundTaskQueue(int capacity)
        {
            //Capacity should be set based on the expected application load and number of concurrent threads accessing the queue.
            //BoundedChannelFullModel.Wait will cause calls to WritAsync() to return a task,
            //which completes only when space became available. this leads to backpressure, 
            //in case too many publisher/calls start accumulating.
            var options = new BoundedChannelOptions(capacity)
            {
                FullMode = BoundedChannelFullMode.Wait
            };
            _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
           
        }

        public async ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken,ValueTask> workItem)
        {
            if(workItem ==null)
                throw new ArgumentNullException(nameof(workItem));
            await _queue.Writer.WriteAsync(workItem); 
        }

        public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(CancellationToken cancellationToken)
        {
            var workItem = await _queue.Reader.ReadAsync(cancellationToken);
            return workItem;
        }
    }
Background task queue
复制代码
复制代码
   public class QueuedHostedService : BackgroundService
    {
        private readonly ILogger<QueuedHostedService> _logger;
        public IBackgroundTaskQueue _taskQueue;

        public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger)
        {
            this._taskQueue = taskQueue;
            this._logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation($"Queued Hosted Service is running. background queue.");
            while (!stoppingToken.IsCancellationRequested)
            {
                var workItem = await _taskQueue.DequeueAsync(stoppingToken);
                try
                {
                    await workItem(stoppingToken);
                    _logger.LogDebug($"The work item is excuting, time: {DateTime.Now}");
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Failed to excute background processing method, exception:{ex}");
                }
            }
        }

        public override async Task StopAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Queued Hosted Service is stopping.");
            await base.StopAsync(stoppingToken);
        }
    }
Queue Hostted Service
复制代码
复制代码
    public class MonitorLoop
    {
        private readonly IBackgroundTaskQueue _taskQueue;
        private readonly ILogger _logger;
        private readonly CancellationToken _cancellationToken;

        public MonitorLoop(IBackgroundTaskQueue taskQueue,
            ILogger<MonitorLoop> logger,
            IHostApplicationLifetime applicationLifetime)
        {
            _taskQueue = taskQueue;
            _logger = logger;
            _cancellationToken = applicationLifetime.ApplicationStopping;
        }

        public void StartMonitorLoop()
        {
            _logger.LogInformation($"MonitorAsync loop is starting");
            Task.Run(async () => await MonitorAsync());
        }

        private async ValueTask MonitorAsync()
        {
            _logger.LogDebug($"Monitor async method is excuting.");
            while (!_cancellationToken.IsCancellationRequested)
            {
                var keyStroke = Console.ReadKey();
                if (keyStroke.Key == ConsoleKey.W)
                {
                    //Enqueue a background work item
                    await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
                }
            }
        }

        private async ValueTask BuildWorkItem(CancellationToken token)
        {
            //simulate three 5-second tasks to complete
            //for each enqueue work item
            int delayLoop = 0;
            var guid = Guid.NewGuid().ToString();
            _logger.LogInformation($"Queue background task {guid} is starting.");
            while(!token.IsCancellationRequested && delayLoop < 3) 
            {
                try
                {
                    _logger.LogDebug($"Start to enqueue the workItem to queue.");
                    await Task.Delay(TimeSpan.FromSeconds(5), token);
                }
                catch (OperationCanceledException ex)
                {
                    _logger.LogWarning($"The queue background task: {guid} is cancelled, exception: {ex}.");
                }
                delayLoop++;
                _logger.LogInformation($"Queue background task {guid} is running, delay loop: {delayLoop}");
            }
            if (delayLoop == 3)
                _logger.LogInformation($"Queue background task : {guid} is complete.");
            else
                _logger.LogInformation($"Queue background task : {guid} was cancelled.");
        }
    }
Moniteor loop
复制代码
复制代码
        public static void Main(string[] args)
        {
            var builder = CreateHostBuilder(args).Build();
            var monitorloop = builder.Services.GetRequiredService<MonitorLoop>();
            monitorloop.StartMonitorLoop();
            builder.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    //services.AddHostedService<Worker>();

                    //services.AddScoped<IScopeProcessingService, ScopedProcessingService>();
                    //services.AddHostedService<ConsumeScopedServiceHostedService>();

                    services.AddSingleton<MonitorLoop>();
                    services.AddHostedService<QueuedHostedService>();
                    services.AddSingleton<IBackgroundTaskQueue>(ctx => 
                    {
                        if (!int.TryParse(hostContext.Configuration["queueCapacity"], out var queueCapacity))
                            queueCapacity = 100;
                        return new BackgroundTaskQueue(queueCapacity);
                    });
                });
Configuration
复制代码

 

posted @   云霄宇霁  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示