Úlohy na pozadí s hostovanými službami v ASP.NET Core

Jeow li Huan

v ASP.NET Core se úlohy na pozadí dají implementovat jako hostované služby. Hostovaná služba je třída s logikou úlohy na pozadí, která implementuje IHostedService rozhraní. Tento článek poskytuje tři příklady hostovaných služeb:

Zobrazit nebo stáhnout ukázkový kód (Jak stáhnout)

Šablona služby pracovní proces

šablona služby ASP.NET Core worker poskytuje výchozí bod pro psaní dlouhotrvajících aplikací služeb. Aplikace vytvořená ze šablony pracovní služby určuje sadu SDK pracovního procesu v jeho souboru projektu:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Použití šablony jako základu pro aplikaci hostované služby:

  1. Vytvoření nového projektu
  2. Vyberte Službu pracovního procesu. Vyberte Další.
  3. Do pole název Project projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Další.
  4. V dialogovém okně Další informace zvolte rozhraní. Vyberte Vytvořit.

Balíček

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a má explicitní odkaz na balíček na balíček Microsoft. Extensions. hosting . Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample. csproj).

U webových aplikací, které používají Microsoft.NET.Sdk.Web sadu SDK, se na balíček Microsoft. Extensions. hosting odkazuje implicitně ze sdíleného rozhraní. V souboru projektu aplikace není vyžadován explicitní odkaz na balíček.

Rozhraní IHostedService

IHostedServiceRozhraní definuje dvě metody pro objekty, které jsou spravovány hostitelem:

StartAsync

StartAsync obsahuje logiku pro zahájení úlohy na pozadí. StartAsync se volá před:

Výchozí chování se dá změnit tak, aby se hostovaná služba StartAsync spouštěla po nakonfigurování kanálu aplikace a ApplicationStarted zavolala se. Chcete-li změnit výchozí chování, přidejte hostovanou službu ( VideosWatcher v následujícím příkladu) po volání CreateBuilder :

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHostedService<VideosWatcher>();}

StopAsync

Token zrušení má výchozí 5 sekundový časový limit, který označuje, že proces vypnutí by již neměl být řádný. Když se na tokenu požaduje zrušení:

  • Všechny zbývající operace na pozadí, které aplikace provádí, by měly být přerušeny.
  • Všechny metody, které jsou volány v nástroji, StopAsync by měly vracet výzvu.

Úkoly se ale po zrušení žádosti zruší, protože — volající čeká na dokončení všech úkolů.

Pokud se aplikace neočekávaně ukončí (například proces aplikace se nezdařil), StopAsync nemusí být volána. Proto žádné metody, které jsou volány nebo operací, které jsou prováděny v nástroji, nemusí StopAsync nastat.

Chcete-li zvětšit výchozí časový limit pěti sekund, nastavte:

Hostovaná služba se aktivuje při spuštění aplikace a řádně se vypíná při vypnutí aplikace. Pokud je vyvolána chyba během provádění úlohy na pozadí, Dispose měla by být volána i v případě, že StopAsync není volána.

Základní třída BackgroundService

BackgroundService je základní třídou pro implementaci dlouhého běhu IHostedService .

Metody ExecuteAsync (CancellationToken) se volá za účelem spuštění služby na pozadí. Implementace vrátí Task , která představuje celou dobu života služby na pozadí. Žádné další služby nejsou spuštěny, dokud metody ExecuteAsync nebude asynchronní, například voláním await . Vyhněte se provádění dlouhého a blokujícího inicializačního fungování v ExecuteAsync . Hostitelské bloky v StopAsync (CancellationToken) čekají na ExecuteAsync dokončení.

Token zrušení se aktivuje, když se zavolá IHostedService. StopAsync . Vaše implementace ExecuteAsync by měla být dokončena okamžitě při vyvolání tokenu zrušení, aby bylo možné službu řádně vypnout. V opačném případě se služba nekorektně ukončí v časovém limitu vypnutí. Další informace najdete v části rozhraní IHostedService .

StartAsync by měl být omezený na krátkodobé spuštěné úlohy, protože hostované služby se spouštějí postupně a žádné další služby nejsou spuštěny, dokud nebude StartAsync dokončeno spuštění. Dlouho běžící úlohy by měly být umístěny v ExecuteAsync . Další informace najdete v tématu zdroj, který se BackgroundService.

Časované úlohy na pozadí

Úloha s časovým limitem na pozadí využívá třídu System. Threading. Timer . Časovač aktivuje DoWork metodu úkolu. Časovač je zakázán StopAsync a vyřazen při vyřazení kontejneru služby Dispose :

public 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 stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

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

Nečeká na Timer Dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář. Prolínání. přírůstek se používá ke zvýšení čítače spouštění jako atomické operace, což zajistí, že se více vláken současně neaktualizuje executionCount .

Služba je registrovaná v IHostBuilder.ConfigureServices (program. cs) s AddHostedService metodou rozšíření:

services.AddHostedService<TimedHostedService>();

Využívání vymezené služby v úloze na pozadí

Pokud chcete používat vymezené služby v rámci BackgroundService, vytvořte obor. Ve výchozím nastavení není pro hostovanou službu vytvořen žádný obor.

Služba úlohy vymezeného na pozadí obsahuje logiku úlohy na pozadí. V následujícím příkladu:

  • Služba je asynchronní. DoWorkMetoda vrátí Task . Pro demonstrační účely je v metodě očekáváno zpoždění deseti sekund DoWork .
  • ILoggerDo služby se vloží.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _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);
        }
    }
}

Hostovaná služba vytvoří obor pro rozpoznání služby úlohy na pozadí pro volání DoWork metody. DoWork Vrátí Task , který je očekáván v ExecuteAsync :

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        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 async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

Služby jsou registrovány v IHostBuilder.ConfigureServices (program. cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Úlohy na pozadí zařazené do fronty

Fronta úloh na pozadí je založena na rozhraní .NET 4. x QueueBackgroundWorkItem :

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

    ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

V následujícím QueueHostedService příkladu:

  • BackgroundProcessingMetoda vrátí hodnotu Task , která je očekávána v ExecuteAsync .
  • Úlohy na pozadí ve frontě se odřadí a spustí v BackgroundProcessing .
  • Pracovní položky jsou očekávány před zastavením služby StopAsync .
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

MonitorLoopSlužba zpracovává enqueuing úlohy pro hostovanou službu vždy, když w je vybraný klíč na vstupním zařízení:

  • IBackgroundTaskQueueJe vložen do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána pro zařazení pracovní položky do fronty.
  • Pracovní položka simuluje dlouho běžící úlohu na pozadí:
    • 3 5 – jsou spuštěny sekundy s prodlevou ( Task.Delay ).
    • try-catchPříkaz zachytávání, OperationCanceledException Pokud je úkol zrušen.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("MonitorAsync Loop is starting.");

        // Run a console user input loop in a background thread
        Task.Run(async () => await MonitorAsync());
    }

    private async ValueTask MonitorAsync()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

        _logger.LogInformation("Queued Background Task {Guid} is starting.", guid);

        while (!token.IsCancellationRequested && delayLoop < 3)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
            catch (OperationCanceledException)
            {
                // Prevent throwing if the Delay is cancelled
            }

            delayLoop++;

            _logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

Služby jsou registrovány v IHostBuilder.ConfigureServices (program. cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop je spuštěn v programu program. cs:

var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();

Další zdroje informací

v ASP.NET Core se úlohy na pozadí dají implementovat jako hostované služby. Hostovaná služba je třída s logikou úlohy na pozadí, která implementuje IHostedService rozhraní. Tento článek poskytuje tři příklady hostovaných služeb:

Zobrazit nebo stáhnout ukázkový kód (Jak stáhnout)

Šablona služby pracovní proces

šablona služby ASP.NET Core worker poskytuje výchozí bod pro psaní dlouhotrvajících aplikací služeb. Aplikace vytvořená ze šablony pracovní služby určuje sadu SDK pracovního procesu v jeho souboru projektu:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Použití šablony jako základu pro aplikaci hostované služby:

  1. Vytvoření nového projektu
  2. Vyberte Službu pracovního procesu. Vyberte Další.
  3. Do pole Název projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Vytvořit.
  4. V dialogovém okně Create a new Worker service (Vytvořit novou službu pracovního procesu) vyberte Create (Vytvořit).

Balíček

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a má explicitní odkaz na balíček na balíček Microsoft. Extensions. hosting . Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample. csproj).

U webových aplikací, které používají Microsoft.NET.Sdk.Web sadu SDK, se na balíček Microsoft. Extensions. hosting odkazuje implicitně ze sdíleného rozhraní. V souboru projektu aplikace není vyžadován explicitní odkaz na balíček.

Rozhraní IHostedService

IHostedServiceRozhraní definuje dvě metody pro objekty, které jsou spravovány hostitelem:

StartAsync

StartAsync obsahuje logiku pro zahájení úlohy na pozadí. StartAsync se volá před:

Výchozí chování se dá změnit tak, aby se hostovaná služba StartAsync spouštěla po nakonfigurování kanálu aplikace a ApplicationStarted zavolala se. Chcete-li změnit výchozí chování, přidejte hostovanou službu ( VideosWatcher v následujícím příkladu) po volání ConfigureWebHostDefaults :

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .ConfigureServices(services =>
            {
                services.AddHostedService<VideosWatcher>();
            });
}

StopAsync

Token zrušení má výchozí 5 sekundový časový limit, který označuje, že proces vypnutí by již neměl být řádný. Když se na tokenu požaduje zrušení:

  • Všechny zbývající operace na pozadí, které aplikace provádí, by měly být přerušeny.
  • Všechny metody, které jsou volány v nástroji, StopAsync by měly vracet výzvu.

Úkoly se ale po zrušení žádosti zruší, protože — volající čeká na dokončení všech úkolů.

Pokud se aplikace neočekávaně ukončí (například proces aplikace se nezdařil), StopAsync nemusí být volána. Proto žádné metody, které jsou volány nebo operací, které jsou prováděny v nástroji, nemusí StopAsync nastat.

Chcete-li zvětšit výchozí časový limit pěti sekund, nastavte:

Hostovaná služba se aktivuje při spuštění aplikace a řádně se vypíná při vypnutí aplikace. Pokud je vyvolána chyba během provádění úlohy na pozadí, Dispose měla by být volána i v případě, že StopAsync není volána.

Základní třída BackgroundService

BackgroundService je základní třídou pro implementaci dlouhého běhu IHostedService .

Metody ExecuteAsync (CancellationToken) se volá za účelem spuštění služby na pozadí. Implementace vrátí Task , která představuje celou dobu života služby na pozadí. Žádné další služby nejsou spuštěny, dokud metody ExecuteAsync nebude asynchronní, například voláním await . Vyhněte se provádění dlouhého a blokujícího inicializačního fungování v ExecuteAsync . Hostitelské bloky v StopAsync (CancellationToken) čekají na ExecuteAsync dokončení.

Token zrušení se aktivuje, když se zavolá IHostedService. StopAsync . Vaše implementace ExecuteAsync by měla být dokončena okamžitě při vyvolání tokenu zrušení, aby bylo možné službu řádně vypnout. V opačném případě se služba nekorektně ukončí v časovém limitu vypnutí. Další informace najdete v části rozhraní IHostedService .

StartAsync by měl být omezený na krátkodobé spuštěné úlohy, protože hostované služby se spouštějí postupně a žádné další služby nejsou spuštěny, dokud nebude StartAsync dokončeno spuštění. Dlouho běžící úlohy by měly být umístěny v ExecuteAsync . Další informace najdete v tématu zdroj, který se BackgroundService.

Časované úlohy na pozadí

Úloha s časovým limitem na pozadí využívá třídu System. Threading. Timer . Časovač aktivuje DoWork metodu úkolu. Časovač je zakázán StopAsync a vyřazen při vyřazení kontejneru služby Dispose :

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

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

Nečeká na Timer Dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář. Prolínání. přírůstek se používá ke zvýšení čítače spouštění jako atomické operace, což zajistí, že se více vláken současně neaktualizuje executionCount .

Služba je registrovaná v IHostBuilder.ConfigureServices (program. cs) s AddHostedService metodou rozšíření:

services.AddHostedService<TimedHostedService>();

Využívání vymezené služby v úloze na pozadí

Pokud chcete používat vymezené služby v rámci BackgroundService, vytvořte obor. Ve výchozím nastavení není pro hostovanou službu vytvořen žádný obor.

Služba úlohy vymezeného na pozadí obsahuje logiku úlohy na pozadí. V následujícím příkladu:

  • Služba je asynchronní. DoWorkMetoda vrátí Task . Pro demonstrační účely je v metodě očekáváno zpoždění deseti sekund DoWork .
  • ILoggerDo služby se vloží.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _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);
        }
    }
}

Hostovaná služba vytvoří obor pro rozpoznání služby úlohy na pozadí pro volání DoWork metody. DoWork Vrátí Task , který je očekáván v ExecuteAsync :

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        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 async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

Služby jsou registrovány v IHostBuilder.ConfigureServices (program. cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Úlohy na pozadí zařazené do fronty

Fronta úloh na pozadí je založena na rozhraní .NET 4. x QueueBackgroundWorkItem :

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

    ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

V následujícím QueueHostedService příkladu:

  • BackgroundProcessingMetoda vrátí hodnotu Task , která je očekávána v ExecuteAsync .
  • Úlohy na pozadí ve frontě se odřadí a spustí v BackgroundProcessing .
  • Pracovní položky jsou očekávány před zastavením služby StopAsync .
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

MonitorLoopSlužba zpracovává enqueuing úlohy pro hostovanou službu vždy, když w je vybraný klíč na vstupním zařízení:

  • IBackgroundTaskQueueJe vložen do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána pro zařazení pracovní položky do fronty.
  • Pracovní položka simuluje dlouho běžící úlohu na pozadí:
    • 3 5 – jsou spuštěny sekundy s prodlevou ( Task.Delay ).
    • try-catchPříkaz zachytávání, OperationCanceledException Pokud je úkol zrušen.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("MonitorAsync Loop is starting.");

        // Run a console user input loop in a background thread
        Task.Run(async () => await MonitorAsync());
    }

    private async ValueTask MonitorAsync()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

        _logger.LogInformation("Queued Background Task {Guid} is starting.", guid);

        while (!token.IsCancellationRequested && delayLoop < 3)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
            catch (OperationCanceledException)
            {
                // Prevent throwing if the Delay is cancelled
            }

            delayLoop++;

            _logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

Služby jsou registrovány v IHostBuilder.ConfigureServices (program. cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop je spuštěn v Program.Main :

var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();

Další zdroje informací