dotnet core系列之Background tasks with hosted services (後臺任務)
這篇簡單講asp.net core 中的後臺任務
用到的包:
Microsoft.AspNetCore.App metapackage
或者加入
Microsoft.Extensions.Hosting
一. Timed background tasks(定時後臺任務)
使用到System.Threading.Timer類。定時器觸發任務的DoWork方法。定時器在StopAsync上停止,並且釋放是在Dispose上
internal class TimedHostedService : IHostedService, IDisposable { private readonly ILogger _logger; private Timer _timer; public TimedHostedService(ILogger<TimedHostedService> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is starting."); _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); return Task.CompletedTask; } private void DoWork(object state) { _logger.LogInformation("Timed Background Service is working."); } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is stopping."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } }
服務是在Startup.ConfigureServices上使用AddHostedService擴充套件方法註冊:
services.AddHostedService<TimedHostedService>();
二. Consuming a scoped service in a background task 在後臺任務中執行scoped service
使用IHostService中的scoped services, 建立一個scope. 對於一個hosted service預設沒有scope被建立。
這個scoped 後臺任務服務包含後臺任務邏輯。下面的例子中,一個ILogger被注入到了service中:
internal interface IScopedProcessingService { void DoWork(); } internal class ScopedProcessingService : IScopedProcessingService { private readonly ILogger _logger; public ScopedProcessingService(ILogger<ScopedProcessingService> logger) { _logger = logger; } public void DoWork() { _logger.LogInformation("Scoped Processing Service is working."); } }
這個hosted service 建立了一個scope解析了scoped後臺任務服務來呼叫它的DoWork方法:
internal class ConsumeScopedServiceHostedService : IHostedService { private readonly ILogger _logger; public ConsumeScopedServiceHostedService(IServiceProvider services, ILogger<ConsumeScopedServiceHostedService> logger) { Services = services; _logger = logger; } public IServiceProvider Services { get; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation( "Consume Scoped Service Hosted Service is starting."); DoWork(); return Task.CompletedTask; } private void DoWork() { _logger.LogInformation( "Consume Scoped Service Hosted Service is working."); using (var scope = Services.CreateScope()) { var scopedProcessingService = scope.ServiceProvider .GetRequiredService<IScopedProcessingService>(); scopedProcessingService.DoWork(); } } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation( "Consume Scoped Service Hosted Service is stopping."); return Task.CompletedTask; } }
服務註冊在Startup.ConfigureServices中。IHostedService的實現用AddHostedService擴充套件方法註冊:
services.AddHostedService<ConsumeScopedServiceHostedService>(); services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
三. Queued background tasks 排隊的後臺任務
public interface IBackgroundTaskQueue { void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem); Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken); } public class BackgroundTaskQueue : IBackgroundTaskQueue { private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>(); private SemaphoreSlim _signal = new SemaphoreSlim(0); public void QueueBackgroundWorkItem( Func<CancellationToken, Task> workItem) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } _workItems.Enqueue(workItem); _signal.Release(); } public async Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken) { await _signal.WaitAsync(cancellationToken); _workItems.TryDequeue(out var workItem); return workItem; } }
在 QueueHostedService中,佇列中的後臺任務出佇列並且作為BackroundService執行。BackgroundService是一個實現了IHostedService介面的類。
public class QueuedHostedService : BackgroundService { private readonly ILogger _logger; public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory) { TaskQueue = taskQueue; _logger = loggerFactory.CreateLogger<QueuedHostedService>(); } public IBackgroundTaskQueue TaskQueue { get; } protected async override Task ExecuteAsync( CancellationToken cancellationToken) { _logger.LogInformation("Queued Hosted Service is starting."); while (!cancellationToken.IsCancellationRequested) { var workItem = await TaskQueue.DequeueAsync(cancellationToken); try { await workItem(cancellationToken); } catch (Exception ex) { _logger.LogError(ex, $"Error occurred executing {nameof(workItem)}."); } } _logger.LogInformation("Queued Hosted Service is stopping."); } }
服務註冊在Startup.ConfigureService方法中。IHostedService的實現用AddHostedService擴充套件方法註冊:
services.AddHostedService<QueuedHostedService>(); services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
在Index page model類中:
- IBackgroundTaskQueue被注入到建構函式並且指定給Queue
- 一個 IServiceScopeFactory被注入並且指定給_serviceScopeFactory. 這個工廠用來建立IServiceScope例項, IServiceScope例項是用來在scope內建立 services的。一個scope被建立時為了用應用的AppDbContext(a scoped service)來寫資料庫記錄在 IBackgroundTaskQueue 中(a singleton service).
public class IndexModel : PageModel { private readonly AppDbContext _db; private readonly ILogger _logger; private readonly IServiceScopeFactory _serviceScopeFactory; public IndexModel(AppDbContext db, IBackgroundTaskQueue queue, ILogger<IndexModel> logger, IServiceScopeFactory serviceScopeFactory) { _db = db; _logger = logger; Queue = queue; _serviceScopeFactory = serviceScopeFactory; } public IBackgroundTaskQueue Queue { get; }
當 Index page 上的Add Task按鈕被選中時,OnPostAddTask方法被執行。QueueBackgroundWorkItem被呼叫來使work item入隊。
public IActionResult OnPostAddTaskAsync() { Queue.QueueBackgroundWorkItem(async token => { var guid = Guid.NewGuid().ToString(); using (var scope = _serviceScopeFactory.CreateScope()) { var scopedServices = scope.ServiceProvider; var db = scopedServices.GetRequiredService<AppDbContext>(); for (int delayLoop = 1; delayLoop < 4; delayLoop++) { try { db.Messages.Add( new Message() { Text = $"Queued Background Task {guid} has " + $"written a step. {delayLoop}/3" }); await db.SaveChangesAsync(); } catch (Exception ex) { _logger.LogError(ex, "An error occurred writing to the " + $"database. Error: {ex.Message}"); } await Task.Delay(TimeSpan.FromSeconds(5), token); } } _logger.LogInformation( $"Queued Background Task {guid} is complete. 3/3"); }); return RedirectToPage(); }
四. 總結
注意上面的方法都有一個共同點:即直接或間接實現 IHostedService 方法
IHostedService interface
Hosted servcies實現IHostService介面. 這個介面定義了兩個方法,為被主機管理的物件:
- StartAsync - StartAsync包含啟動後臺任務的邏輯。
- StopAsync - 當host 執行關閉時觸發。StopAsync包含終止後臺任務的邏輯。實現IDisposable 和finalizers 來釋放任意unmanaged resources.
參考網址:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio
&n