Sdílet prostřednictvím


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

Autor: Jeow Li Huan

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:

  • Úloha na pozadí, která běží v časovači.
  • Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
  • Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.

Šablona služby pracovního procesu

Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:

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

Použití šablony jako základu pro aplikaci hostovaných služeb:

  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 Další.
  4. V dialogovém okně Další informace zvolte architekturu. Vyberte Vytvořit.

Balíček

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a obsahuje explicitní odkaz 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 implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.

Rozhraní IHostedService

Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:

StartAsync

StartAsync(CancellationToken) obsahuje logiku pro spuštění úlohy na pozadí. StartAsync je volána před:

StartAsync měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync se nespustí do dokončení.

StopAsync

Token zrušení má výchozí 30sekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:

  • Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
  • Všechny metody volané volané by StopAsync se měly okamžitě vrátit.

Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.

Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync nebo provedli operace.

Pokud chcete prodloužit výchozí časový limit 30sekundového vypnutí, nastavte:

Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose v případě, že StopAsync není volána.

Základní třída BackgroundService

BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.

Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync dokončení.

Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.

Další informace najdete ve zdrojovém kódu BackgroundService .

Úkoly na pozadí s časovým limitem

Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork úlohy. Časovač je zakázán StopAsync a uvolněn, když je kontejner služby uvolněn: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á Timer na dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount souběžně.

Služba je zaregistrovaná v IHostBuilder.ConfigureServices (Program.cs) pomocí AddHostedService metody rozšíření:

services.AddHostedService<TimedHostedService>();

Využívání služby s vymezeným oborem v úloze na pozadí

Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.

Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:

  • Služba je asynchronní. Metoda DoWork vrátí Taskhodnotu . Pro demonstrační účely je v DoWork metodě očekáváno zpoždění deseti sekund.
  • Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork metodu. DoWork vrátí hodnotu Task, která je očekávána 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí AddHostedService metody rozšíření:

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

Úlohy na pozadí ve frontě

Fronta úloh na pozadí je založená 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:

  • Metoda BackgroundProcessing vrátí Taskhodnotu , která je očekávána v ExecuteAsync.
  • Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v BackgroundProcessing.
  • Pracovní položky jsou očekávána před zastavením StopAsyncslužby .
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);
    }
}

MonitorLoop Služba zpracovává úlohy zařazení do fronty hostované služby při každém w výběru klíče na vstupním zařízení:

  • Vloží se IBackgroundTaskQueue do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána k vytvoření fronty pracovní položky.
  • Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
    • Spustí se tři 5sekundová zpoždění (Task.Delay).
    • Příkaz try-catch zachytí 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí 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.cs:

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

Asynchronní časovaná úloha na pozadí

Následující kód vytvoří asynchronní časově uspořádanou úlohu na pozadí:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

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

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

        // When the timer should have no due-time, then do the work once now.
        DoWork();

        using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));

        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                DoWork();
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Timed Hosted Service is stopping.");
        }
    }

    // Could also be a async method, that can be awaited in ExecuteAsync above
    private void DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

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

Nativní AOT

Šablony služby Pracovních procesů podporují nativní .NET předem (AOT) s příznakem --aot :

  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 Další.
  4. V dialogovém okně Další informace :
  5. Zvolte architekturu.
  6. Zaškrtněte políčko Povolit nativní publikování AOT.
  7. Vyberte Vytvořit.

Možnost AOT přidá <PublishAot>true</PublishAot> do souboru projektu:


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

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
+   <PublishAot>true</PublishAot>
    <UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
  </ItemGroup>
</Project>

Další materiály

V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:

  • Úloha na pozadí, která běží v časovači.
  • Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
  • Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.

Šablona služby pracovního procesu

Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:

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

Použití šablony jako základu pro aplikaci hostovaných služeb:

  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 Další.
  4. V dialogovém okně Další informace zvolte architekturu. Vyberte Vytvořit.

Balíček

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a obsahuje explicitní odkaz 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 implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.

Rozhraní IHostedService

Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:

StartAsync

StartAsync(CancellationToken) obsahuje logiku pro spuštění úlohy na pozadí. StartAsync je volána před:

StartAsync měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync se nespustí do dokončení.

StopAsync

Token zrušení má výchozí 30sekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:

  • Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
  • Všechny metody volané volané by StopAsync se měly okamžitě vrátit.

Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.

Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync nebo provedli operace.

Pokud chcete prodloužit výchozí časový limit 30sekundového vypnutí, nastavte:

Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose v případě, že StopAsync není volána.

Základní třída BackgroundService

BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.

Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync dokončení.

Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.

Další informace najdete ve zdrojovém kódu BackgroundService .

Úkoly na pozadí s časovým limitem

Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork úlohy. Časovač je zakázán StopAsync a uvolněn, když je kontejner služby uvolněn: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á Timer na dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount souběžně.

Služba je zaregistrovaná v IHostBuilder.ConfigureServices (Program.cs) pomocí AddHostedService metody rozšíření:

services.AddHostedService<TimedHostedService>();

Využívání služby s vymezeným oborem v úloze na pozadí

Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.

Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:

  • Služba je asynchronní. Metoda DoWork vrátí Taskhodnotu . Pro demonstrační účely je v DoWork metodě očekáváno zpoždění deseti sekund.
  • Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork metodu. DoWork vrátí hodnotu Task, která je očekávána 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí AddHostedService metody rozšíření:

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

Úlohy na pozadí ve frontě

Fronta úloh na pozadí je založená 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:

  • Metoda BackgroundProcessing vrátí Taskhodnotu , která je očekávána v ExecuteAsync.
  • Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v BackgroundProcessing.
  • Pracovní položky jsou očekávána před zastavením StopAsyncslužby .
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);
    }
}

MonitorLoop Služba zpracovává úlohy zařazení do fronty hostované služby při každém w výběru klíče na vstupním zařízení:

  • Vloží se IBackgroundTaskQueue do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána k vytvoření fronty pracovní položky.
  • Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
    • Spustí se tři 5sekundová zpoždění (Task.Delay).
    • Příkaz try-catch zachytí 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí 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.cs:

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

Asynchronní časovaná úloha na pozadí

Následující kód vytvoří asynchronní časově uspořádanou úlohu na pozadí:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

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

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

        // When the timer should have no due-time, then do the work once now.
        DoWork();

        using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));

        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                DoWork();
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Timed Hosted Service is stopping.");
        }
    }

    // Could also be a async method, that can be awaited in ExecuteAsync above
    private void DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

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

Další materiály

V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:

  • Úloha na pozadí, která běží v časovači.
  • Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
  • Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Šablona služby pracovního procesu

Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:

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

Použití šablony jako základu pro aplikaci hostovaných služeb:

  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ě Vytvořit novou službu pracovního procesu vyberte Vytvořit.

Balíček

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a obsahuje explicitní odkaz 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 implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.

Rozhraní IHostedService

Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:

StartAsync

StartAsync obsahuje logiku pro spuštění úlohy na pozadí. StartAsync je volána před:

Výchozí chování je možné změnit tak, aby se hostovaná služba StartAsync spustila po nakonfigurování kanálu aplikace a ApplicationStarted zavolána. Pokud chcete změnit výchozí chování, přidejte hostované služby (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í pětisekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:

  • Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
  • Všechny metody volané volané by StopAsync se měly okamžitě vrátit.

Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.

Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync nebo provedli operace.

Pokud chcete prodloužit výchozí časový limit pětisekundového vypnutí, nastavte:

Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose v případě, že StopAsync není volána.

Základní třída BackgroundService

BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.

Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync dokončení.

Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.

StartAsync měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync se nespustí do dokončení. Dlouhotrvající úkoly by měly být umístěny do ExecuteAsyncsouboru . Další informace najdete ve zdroji služby BackgroundService.

Úkoly na pozadí s časovým limitem

Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork úlohy. Časovač je zakázán StopAsync a uvolněn, když je kontejner služby uvolněn: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á Timer na dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount souběžně.

Služba je zaregistrovaná v IHostBuilder.ConfigureServices (Program.cs) pomocí AddHostedService metody rozšíření:

services.AddHostedService<TimedHostedService>();

Využívání služby s vymezeným oborem v úloze na pozadí

Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.

Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:

  • Služba je asynchronní. Metoda DoWork vrátí Taskhodnotu . Pro demonstrační účely je v DoWork metodě očekáváno zpoždění deseti sekund.
  • Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork metodu. DoWork vrátí hodnotu Task, která je očekávána 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí AddHostedService metody rozšíření:

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

Úlohy na pozadí ve frontě

Fronta úloh na pozadí je založená 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:

  • Metoda BackgroundProcessing vrátí Taskhodnotu , která je očekávána v ExecuteAsync.
  • Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v BackgroundProcessing.
  • Pracovní položky jsou očekávána před zastavením StopAsyncslužby .
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);
    }
}

MonitorLoop Služba zpracovává úlohy zařazení do fronty hostované služby při každém w výběru klíče na vstupním zařízení:

  • Vloží se IBackgroundTaskQueue do MonitorLoop služby.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána k vytvoření fronty pracovní položky.
  • Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
    • Spustí se tři 5sekundová zpoždění (Task.Delay).
    • Příkaz try-catch zachytí 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 zaregistrované v IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná pomocí 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ší materiály