這篇簡單講asp.net core 中的后台任務
用到的包:
Microsoft.AspNetCore.App metapackage
或者加入
一. 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.
你可以把這種用法的后台任務加到任意應用,例如web api , mvc , 控制台等,因為后台服務在應用啟動時,就被加載了。它是被以服務的方式加到了管道上了
參考網址: