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.

你可以把這種用法的后台任務加到任意應用,例如web api , mvc , 控制台等,因為后台服務在應用啟動時,就被加載了。它是被以服務的方式加到了管道上了

參考網址:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM