学海无涯

导航

统计

在 ASP.NET Core 中使用托管服务实现后台任务

在 ASP.NET Core 中,后台任务作为托管服务实现。 托管服务是一个类,具有实现 IHostedService 接口的后台任务逻辑。 本文提供了三个托管服务示例:

程序包

基于辅助角色服务模板的应用使用 Microsoft.NET.Sdk.Worker SDK,并且具有对 Microsoft.Extensions.Hosting 包的显式包引用。 有关示例,请参阅示例应用的项目文件 (BackgroundTasksSample.csproj)。

对于使用 Microsoft.NET.Sdk.Web SDK 的 Web 应用,通过共享框架隐式引用 Microsoft.Extensions.Hosting 包。 在应用的项目文件中不需要显式包引用。

IHostedService 接口(仅限于短期任务)

IHostedService 接口为主机托管的对象定义了两种方法:

StartAsync

StartAsync(CancellationToken) 包含用于启动后台任务的逻辑。 在以下操作之前调用 StartAsync

StartAsync 应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync 运行完成之前不会启动其他服务。

StopAsync

BackgroundService 基类(实现长时间运行)

BackgroundService 是用于实现长时间运行的 IHostedService 的基类。

调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。 在 ExecuteAsync 变为异步(例如通过调用 await)之前,不会启动任何其他服务。 避免在 ExecuteAsync 中执行长时间的阻塞初始化工作。 StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync

调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync 的实现应立即完成。 否则,服务将在关闭超时后不正常关闭

定时执行的计数器后台任务

定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork 方法。 在 StopAsync 上禁用计时器,并在 Dispose 上处置服务容器时处置计时器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WorkerService1
{
    /// <summary>
    /// 计数器的后台任务
    /// </summary>
    internal class TimedHostedService : IHostedService, IDisposable
    {
        private int executionCount = 0;
        private readonly ILogger<TimedHostedService> _logger;
        private Timer? _timer = null;
        public TimedHostedService(ILogger<TimedHostedService> logger)
        {
            _logger = logger;
        }
 
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("开始: Timed Hosted Service running.");
            //Timer 不等待先前的 DoWork 执行完成
            //Timer 参数说明:1.回调方法,2.传递给回调方法的参数,3.多长时间开始执行回调,4.间隔多久执行一次回调
            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(3));
            return Task.CompletedTask;
        }
 
        private void DoWork(object? state)
        {
            //使用 Interlocked.Increment 以原子操作的形式将执行计数器递增,这可确保多个线程不会并行更新 executionCount
            var count = Interlocked.Increment(ref executionCount);
            _logger.LogInformation($"计数工作: Timed Hosted Service is working. Count:{count} {DateTime.Now}");
        }
 
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("结束: Time Hosted Service is stopping.");
            _timer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }
 
        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
}

已使用 AddHostedService 扩展方法在 IHostBuilder.ConfigureServices (Program.cs) 中注册该服务:

1
builder.Services.AddHostedService<TimedHostedService>();

在后台任务中使用有作用域的服务

要在 BackgroundService 中使用有作用域的服务,请创建作用域。 默认情况下,不会为托管服务创建作用域。

作用域后台任务服务包含后台任务的逻辑。 如下示例中:

  • 服务是异步的。 DoWork 方法返回 Task。 出于演示目的,在 DoWork 方法中等待 10 秒的延迟。
  • ILogger 注入到服务中。

将要执行的服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger<ScopedProcessingService> _logger;
 
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }
 
    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;
            _logger.LogInformation("Scoped Processing Service is working. Count:{Count}", executionCount);
            await Task.Delay(10000, stoppingToken);
        }
    }
} 

后台任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
internal class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
    public IServiceProvider Services { get; }
    public ConsumeScopedServiceHostedService(IServiceProvider service, ILogger<ConsumeScopedServiceHostedService> logger)
    {
        _logger = logger;
        this.Services = service;
    }
 
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Consume Scoped Service Hosted Service runing.");
        await DoWork(stoppingToken);
    }
    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Consume Scoped Service Hosted Service is working.");
        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();
            await scopedProcessingService.DoWork(stoppingToken);
        }
    }
    public override Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Consume Scoped Service Hosted Service is stopping.");
        return base.StopAsync(cancellationToken);
    }
} 

注册服务

1
2
builder.Services.AddHostedService<ConsumeScopedServiceHostedService>();
builder.Services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

异步定时后台任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WorkerService1
{
    internal class TimedHostedServiceAsync : BackgroundService
    {
        private readonly ILogger<TimedHostedServiceAsync> _logger;
 
        public TimedHostedServiceAsync(ILogger<TimedHostedServiceAsync> logger)
        {
            _logger = logger;
        }
 
        private int _executionCount;
 
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Timed Hosted Service running.");
            //启动时先执行一次
            await DoWork();
            //下次执行时等待周期,即间隔4个小时间执行一次?
            using PeriodicTimer time = new PeriodicTimer(TimeSpan.FromSeconds(5));
            try
            {
                while (await time.WaitForNextTickAsync(stoppingToken))
                {
                    await DoWork();
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("Timed Hosted Service is stopping.");
            }
        }
 
        private async Task DoWork()
        {
            int count = Interlocked.Increment(ref _executionCount);
            _logger.LogInformation($"我是5秒执行一次. Count:{count} {DateTime.Now}");
            await Task.Delay(1000);
        }
    }
} 

注册服务

1
builder.Services.AddHostedService<TimedHostedServiceAsync>();

 

 

参考:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-8.0&tabs=visual-studio  

 

  

posted on   宁静致远.  阅读(70)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示