.NET 8 強大功能 IHostedService 與 BackgroundService 實戰

小码编匠發表於2024-11-14

前言

在.NET 8中,IHostedService 和 BackgroundService 兩個核心介面的引入,增強了專案開發中處理定時任務的能力。這兩個介面不僅簡化了定時任務、後臺處理作業以及定期維護任務的實現過程,還提升了在ASP.NET Core 或任何基於.NET的宿主應用程式中的整合與管理效率。

IHostedService介面提供了一個基本的框架,允許自定義後臺服務的啟動和停止邏輯。透過實現該介面,可以靈活地控制服務的生命週期,確保任務在應用程式啟動時自動執行,並在應用程式關閉時結束。

而 BackgroundService 類則是對 IHostedService 介面的進一步封裝,它專為需要長時間執行的任務而設計。

透過繼承 BackgroundService並重寫其 ExecuteAsync方法,可以輕鬆地實現複雜的後臺邏輯,如迴圈執行的任務、基於時間間隔的操作等。這種設計模式讓程式碼的可讀性和可維護性變的更好。

利用這些功能,可以快速構建出高效、可靠的定時任務系統,用於執行諸如訊息推送、資料更新、定時釋出等關鍵業務操作。這些任務可以在不影響應用程式主流程的情況下獨立執行,從而提高了整個系統的效能和穩定性。

介紹

.NET 中的後臺服務允許在後臺獨立於主應用程式執行緒執行任務。這對於需要連續或定期執行而不阻塞主應用程式流的任務至關重要。

IHostedService

IHostedService 是一個簡單的介面,用於實現後臺服務。當需要實現自定義的託管服務時,可以透過實現這個介面來建立。

該介面定義了兩個方法:StartAsync(CancellationToken cancellationToken) 和 StopAsync(CancellationToken cancellationToken),分別用於啟動和停止服務。

BackgroundService

BackgroundService 是一個抽象類,它繼承自 IHostedService 並提供了更簡潔的方式來編寫後臺服務。它通常用於需要長時間執行的任務,如監聽器、工作佇列處理器等。透過重寫 ExecuteAsync(CancellationToken stoppingToken)方法,可以在其中編寫任務的邏輯。ExecuteAsync方法會迴圈在後臺執行,直到服務停止。

IHostedService 示例

1、註冊服務

Program.cs 中新增配置,.NET 5 及以下在需要在 Startup.cs 註冊服務。

// .NET 8
using ManageCore.Api;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHostedService<DemoHostedService>();
var app = builder.Build();

2、建立服務介面

建立一個類,該類繼承 IHostedService 介面,並實現該介面成員.

在不需要定時執行任務的時候,也可以在這裡進行應用啟動後的操作,例如建立 RabbitMQ 連線

using Microsoft.Extensions.Hosting;

namespace ManageCore.Api
{
    public class DemoHostedService : IHostedService, IDisposable
    {
        private Timer? _timer;

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

            return Task.CompletedTask;
        }

        private void DoWork(object? state)
        {
            Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine("StopAsync");

            return Task.CompletedTask;
        }

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

上面的Demo程式碼非常簡單,應用在執行後,會去執行 StartAsync 函式,應用關閉執行 StopAsync,由於這裡使用的定時器,所以每過5秒都會執行一次 DoWork 函式。

3、執行效果

4、IHostedService 說明

注意:定時是不等待任務執行完成,只要時間一到,就會呼叫 DoWork 函式,所以適合一些簡單、特定的場景。

以下為官方文件對 IHostedService 介面 的說明

IHostedService 介面為主機託管的物件定義了兩種方法:

  • StartAsync(CancellationToken)
  • StopAsync(CancellationToken)

StartAsync(CancellationToken) 包含用於啟動後臺任務的邏輯。 在以下操作之前呼叫 `StartAsync`:已配置應用的請求處理管道。已啟動伺服器且已觸發 IApplicationLifetime.ApplicationStarted。

StartAsync應僅限於短期任務,因為託管服務是按順序執行的,在 StartAsync 執行完成之前不會啟動其他服務。

StopAsync(CancellationToken) 在主機執行正常關閉時觸發。 StopAsync`包含結束後臺任務的邏輯。 實現 IDisposable 和終結器(解構函式)以處置任何非託管資源。

預設情況下,取消令牌會有五秒超時,以指示關閉程序不再正常。 在令牌上請求取消時:

  • 應中止應用正在執行的任何剩餘後臺操作。
  • StopAsync 中呼叫的任何方法都應及時返回。

但是,在請求取消後,將不會放棄任務,呼叫方會等待所有任務完成。

如果應用意外關閉(例如,應用的程序失敗),則可能不會呼叫 StopAsync。 因此,在 StopAsync 中執行的任何方法或操作都可能不會發生。

若要延長預設值為 5 秒的關閉超時值,請設定:

  • ShutdownTimeout(當使用通用主機時)
  • 使用 Web 主機時為關閉超時值主機配置設定

託管服務在應用啟動時啟用一次,在應用關閉時正常關閉。 如果在執行後臺任務期間引發錯誤,即使未呼叫 StopAsync,也應呼叫 Dispose。

BackgroundService 示例

1、註冊服務

首先,同樣需要在配置中註冊服務介面。

using ManageCore.Api;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IDemoTaskWorkService, DemoTaskWorkService>();

2、BackgroundService 原始碼

檢視 BackgroundService 的原始碼,幫助我們理解BackgroundService 實現原理。BackgroundService 是 IHostedService的一個簡單實現,內部 IHostedService 的 StartAsync 呼叫了 ExecuteAsync,本質上就是使用了 IHostedService。

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();
    }
}

3、建立服務介面

建立一個服務介面,定義需要實現的任務,以及對應的實現,如果需要執行非同步方法,記得加上 await,不然任務將不會等待執行結果,直接進行下一個任務。

namespace ManageCore.Api
{
    public interface IDemoTaskWorkService
    {
        /// <summary>
        /// 測試任務
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        Task TaskWorkAsync(CancellationToken stoppingToken);
    }
}
public class DemoTaskWorkService : IDemoTaskWorkService
{
      /// <summary>
      /// 任務執行
      /// </summary>
      /// <param name="stoppingToken"></param>
      /// <returns></returns>
      public async Task TaskWorkAsync(CancellationToken stoppingToken)
      {
          while (!stoppingToken.IsCancellationRequested)
          {
              //執行任務
              Console.WriteLine($"{DateTime.Now}");

              //週期性任務,於上次任務執行完成後,等待5秒,執行下一次任務
              await Task.Delay(500);
          }
      }
 }

建立後臺服務類,繼承基類 BackgroundService,這裡需要注意的是,要在 BackgroundService 中使用有作用域的服務,請建立作用域, 預設情況下,不會為託管服務建立作用域,得自己管理服務的生命週期,切記!於建構函式中注入 IServiceProvider即可。

namespace ManageCore.Api
{
    public class DemoBackgroundService : BackgroundService
    {
        private readonly IServiceProvider _services;

        public DemoBackgroundService(IServiceProvider services)
        {
            _services = services;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            using var scope = _services.CreateScope();

            var taskWorkService = scope.ServiceProvider.GetRequiredService<IDemoTaskWorkService>();

            await taskWorkService.TaskWorkAsync(stoppingToken);
        }
    }
}

DemoBackgroundService類也是需要註冊的,註冊方式與 IHostedService 介面的方式一樣

builder.Services.AddHostedService<DemoBackgroundService>();

4、執行效果

5、BackgroundService 說明

BackgroundService 是用於實現長時間執行的 IHostedService 的基類。

呼叫 ExecuteAsync(CancellationToken) 來執行後臺服務。 實現返回一個 Task,其表示後臺服務的整個生存期。

在 ExecuteAsync 變為非同步(例如透過呼叫 await)之前,不會啟動任何其他服務。 避免在 ExecuteAsync 中執行長時間的阻塞初始化工作。

StopAsync(CancellationToken) 中的主機塊等待完成 ExecuteAsync。

呼叫 IHostedService.StopAsync 時,將觸發取消令牌。 當激發取消令牌以便正常關閉服務時,ExecuteAsync 的實現應立即完成。 否則,服務將在關閉超時後不正常關閉。

StartAsync 應僅限於短期任務,因為託管服務是按順序執行的,在 StartAsync 執行完成之前不會啟動其他服務。

長期任務應放置在 ExecuteAsync 中。

IHostedService 和 BackgroundService 區別

抽象級別

  • IHostedService:需要手動實現啟動和停止邏輯。
  • BackgroundService:透過提供具有要重寫的單個方法的基類來簡化實現。

使用案例

  • IHostedService:適用於需要對服務生命週期進行精細控制的更復雜的方案。
  • BackgroundService:非常適合受益於減少樣板程式碼的更簡單、長時間執行的任務。

總結

總之.NET 8 中的 IHostedService 和 BackgroundService 提供了強大的工具集,使定時任務、後臺處理以及定期維護等功能的實現變得更加直接、高效和靈活。無論是構建複雜的企業級應用還是簡單的服務應用,這兩個元件都能提供穩定且高效的解決方案。

如果你覺得這篇文章對你有幫助,不妨點個贊支援一下!你的支援是我繼續分享知識的動力。如果有任何疑問或需要進一步的幫助,歡迎隨時留言。

也可以加入微信公眾號 [DotNet技術匠] 社群,與其他熱愛技術的同行一起交流心得,共同成長!

相關文章