受欢迎的博客标签

ASP.NET Core Background Service(2)-用IHostedService和BackgroundService实现创建后台任务

Published

内容摘要:

在.net core 3.x体系中,要实现一个后台任务并在操作系统中以服务的方式运行,需要做以下三件事情:

1.后台任务层:选择实现IHostedService接口或继承BackgroundService类或创建Worker service项目之一实现一个后台任务

2.应用程序层:将后台任务在控制台程序或web程序中注入实例

3.操作系统层:将应用程序注册到操作系统中,以服务的方式运行。

在.net 体系中,与后台任务相关的入口有三个:

1.IHostedService接口

2.BackgroundService

3.Worker service

 

一、应用场景

以调用微信公众号的Api为例, 经常会用到access_token,官方文档这样描述:“是公众号的全局唯一接口调用凭据,有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效,建议公众号开发者使用中控服务器统一获取和刷新Access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务。”

在这个场景中我们可以创建一个后台运行的服务,按照access_token的有效期定时执行去请求获取新的access_token并存储,其他所有需要用到这个access_token的都到这个共有的access_token。

二、实现方式(一):实现IHostedService接口

ASP.NET Core 在3.x的时候就提供了一个名为IHostedService的接口,我们要做的只有两件事:

1. 实现它。

2. 将这个接口实现注册到依赖注入服务中。

A. 实现IHostedService的接口

            首先看一下这个IHostedService:

namespace Microsoft.Extensions.Hosting
{
    //
    // Summary:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        //
        // Summary:
        // Triggered when the application host is ready to start the service.
        Task StartAsync(CancellationToken cancellationToken);
        //
        // Summary:
        // Triggered when the application host is performing a graceful shutdown.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

 

通过名字就可以看出来,一个是这个服务启动的时候做的操作,另一个则是停止的时候。

新建一个类 TokenRefreshService  实现 IHostedService ,如下面代码所示:

internal class TokenRefreshService : IHostedService, IDisposable
    {
        private readonly ILogger _logger;
        private Timer _timer;

        public TokenRefreshService(ILogger<TokenRefreshService> logger)
        {
            _logger = logger;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Service starting");
            _timer = new Timer(Refresh, null, TimeSpan.Zero,TimeSpan.FromSeconds(5));
            return Task.CompletedTask;
        }

        private void Refresh(object state)
        {
            _logger.LogInformation(DateTime.Now.ToLongTimeString() + ": Refresh Token!"); //在此写需要执行的任务

        }

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

        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
 

既然是定时刷新任务,那么就用了一个timer, 当服务启动的时候启动它,由它定时执行Refresh方法来获取新的Token。

这里为了方便测试写了5秒执行一次, 实际应用还是读取配置文件比较好, 结果如下:

BackService.TokenRefreshService:Information: 17:23:30: Refresh Token!
 BackService.TokenRefreshService:Information: 17:23:35: Refresh Token!
 BackService.TokenRefreshService:Information: 17:23:40: Refresh Token!
 BackService.TokenRefreshService:Information: 17:23:45: Refresh Token!
 BackService.TokenRefreshService:Information: 17:23:50: Refresh Token!

 B. 在依赖注入中注册这个服务。

在Startup的ConfigureServices中注册这个服务,如下代码所示:

services.AddSingleton<IHostedService, TokenRefreshService>();
 

 

三、实现方式(二):继承BackgroundService(class BackgroundService : IHostedService),并重写ExecuteAsync()函数

 在 ASP.NET Core 3.x中, 提供了一个名为 BackgroundService  的类,它在 Microsoft.Extensions.Hosting 命名空间中,查看一下它的源码:

BackgroundService source code:https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Base class for implementing a long running <see cref="IHostedService"/>.
    /// </summary>
    public abstract class BackgroundService : IHostedService, IDisposable
    {
        private Task _executingTask;
        private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();

        /// <summary>
        /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
        /// the lifetime of the long running operation(s) being performed.
        /// </summary>
        /// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
        /// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
        protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

        /// <summary>
        /// Triggered when the application host is ready to start the service.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
        public virtual Task StartAsync(CancellationToken cancellationToken)
        {
            // Store the task we're executing
            _executingTask = ExecuteAsync(_stoppingCts.Token);

            // If the task is completed then return it, this will bubble cancellation and failure to the caller
            if (_executingTask.IsCompleted)
            {
                return _executingTask;
            }

            // Otherwise it's running
            return Task.CompletedTask;
        }

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
        public virtual async Task StopAsync(CancellationToken cancellationToken)
        {
            // Stop called without start
            if (_executingTask == null)
            {
                return;
            }

            try
            {
                // Signal cancellation to the executing method
                _stoppingCts.Cancel();
            }
            finally
            {
                // Wait until the task completes or the stop token triggers
                await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
            }
        }

        public virtual void Dispose()
        {
            _stoppingCts.Cancel();
        }
    }
}

可以看出它一样是继承自 IHostedService, IDisposable , 它相当于是帮我们写好了一些“通用”的逻辑, 而我们只需要继承并实现它的 ExecuteAsync 即可。

也就是说,我们只需在这个方法内写下这个服务需要做的事,这样上面的刷新Token的Service就可以改写成这样:

internal class TokenRefreshService : BackgroundService
    {
        private readonly ILogger _logger;

        public TokenRefreshService(ILogger<TokenRefresh2Service> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Service starting");

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation(DateTime.Now.ToLongTimeString() + ": Refresh Token!");//在此写需要执行的任务
                await Task.Delay(5000, stoppingToken);
            }

            _logger.LogInformation("Service stopping");
        }
    }

是不是简单了不少。

 

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
 
    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }
 
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation($"Worker running at: {DateTime.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}