Delen via


Achtergrondtaken met gehoste services in ASP.NET Core

Door Jeow Li Huan

Note

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.

Warning

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

Important

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikel voor de huidige release.

In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:

  • Achtergrondtaak die wordt uitgevoerd op een timer.
  • Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
  • Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.

Worker Service-sjabloon

De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:

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

De sjabloon gebruiken als basis voor een gehoste services-app:

  1. Maak een nieuw project.
  2. Selecteer Worker Service. Kies Volgende.
  3. Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
  4. Kies een framework in het dialoogvenster Aanvullende informatie. Klik op Creëren.

Package

Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).

Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.

IHostedService-interface

De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:

StartAsync

StartAsync(CancellationToken) bevat de logica om de achtergrondtaak te starten. StartAsync wordt eerder aangeroepen:

StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid.

StopAsync

Het annuleringstoken heeft een standaard time-out van 30 seconden om aan te geven dat het afsluitproces niet langer op een zachte wijze mag verlopen. Wanneer annulering wordt aangevraagd op het token:

  • Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
  • Alle methoden die binnen StopAsync worden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.

Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.

Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.

Als u de standaard time-out voor afsluiten van 30 seconden wilt uitbreiden, stelt u het volgende in:

De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.

Basisklasse BackgroundService

BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.

ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.

Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.

Zie de broncode van BackgroundService voor meer informatie.

Getimede achtergrondtaken

Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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();
    }
}

Timer wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario. Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.

De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.

services.AddHostedService<TimedHostedService>();

Een scoped service gebruiken in een achtergrondtaak

Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.

De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:

  • De dienst is asynchroon. De DoWork methode retourneert een Task. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in de DoWork methode verwacht.
  • Een ILogger wordt in de service geïnjecteerd.
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);
        }
    }
}

De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen. DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.

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);
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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

Achtergrondtaken in wachtrij

Een achtergrondtaakwachtrij is gebaseerd op .NET Framework 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;
    }
}

In het volgende QueueHostedService voorbeeld:

  • De BackgroundProcessing-methode geeft een Task terug, die wordt afgewacht in ExecuteAsync.
  • Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in BackgroundProcessing.
  • Werkitems worden gewacht voordat de service stopt.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);
    }
}

Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.

  • De IBackgroundTaskQueue wordt in de MonitorLoop service geïnjecteerd.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem wordt aangeroepen om een werkitem in de wachtrij te zetten.
  • Het werkitem simuleert een langlopende achtergrondtaak:
    • Er worden drie vertragingen van 5 seconden uitgevoerd (Task.Delay).
    • Een try-catch instructie vangt OperationCanceledException als de taak wordt geannuleerd.
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);
        }
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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 wordt gestart in Program.cs:

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

Asynchrone getimede achtergrondtaak

Met de volgende code wordt een asynchrone achtergrondtaak met een timer gemaakt.

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.
        await DoWork();

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

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

    private async Task DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

        // Simulate work
        await Task.Delay(TimeSpan.FromSeconds(2));

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

Inheemse AOT

De Worker Service-sjablonen ondersteunen .NET native ahead-of-time (AOT) met de --aot flag.

  1. Maak een nieuw project.
  2. Selecteer Worker Service. Kies Volgende.
  3. Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
  4. In het dialoogvenster Aanvullende informatie :
  5. Kies een framework.
  6. Vink het selectievakje Systeemeigen AOT-publicatie inschakelen aan.
  7. Klik op Creëren.

De AOT-optie voegt <PublishAot>true</PublishAot> toe aan het projectbestand.


<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>

Aanvullende bronnen

In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:

  • Achtergrondtaak die wordt uitgevoerd op een timer.
  • Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
  • Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.

Worker Service-sjabloon

De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:

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

De sjabloon gebruiken als basis voor een gehoste services-app:

  1. Maak een nieuw project.
  2. Selecteer Worker Service. Kies Volgende.
  3. Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Kies Volgende.
  4. Kies een framework in het dialoogvenster Aanvullende informatie. Klik op Creëren.

Package

Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).

Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.

IHostedService-interface

De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:

StartAsync

StartAsync(CancellationToken) bevat de logica om de achtergrondtaak te starten. StartAsync wordt eerder aangeroepen:

StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid.

StopAsync

Het annuleringstoken heeft een standaard time-out van 30 seconden om aan te geven dat het afsluitproces niet langer op een zachte wijze mag verlopen. Wanneer annulering wordt aangevraagd op het token:

  • Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
  • Alle methoden die binnen StopAsync worden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.

Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.

Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.

Als u de standaard time-out voor afsluiten van 30 seconden wilt uitbreiden, stelt u het volgende in:

De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.

Basisklasse BackgroundService

BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.

ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.

Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.

Zie de broncode van BackgroundService voor meer informatie.

Getimede achtergrondtaken

Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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();
    }
}

Timer wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario. Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.

De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.

services.AddHostedService<TimedHostedService>();

Een scoped service gebruiken in een achtergrondtaak

Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.

De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:

  • De dienst is asynchroon. De DoWork methode retourneert een Task. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in de DoWork methode verwacht.
  • Een ILogger wordt in de service geïnjecteerd.
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);
        }
    }
}

De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen. DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.

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);
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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

Achtergrondtaken in wachtrij

Een achtergrondtaakwachtrij is gebaseerd op .NET Framework 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;
    }
}

In het volgende QueueHostedService voorbeeld:

  • De BackgroundProcessing-methode geeft een Task terug, die wordt afgewacht in ExecuteAsync.
  • Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in BackgroundProcessing.
  • Werkitems worden gewacht voordat de service stopt.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);
    }
}

Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.

  • De IBackgroundTaskQueue wordt in de MonitorLoop service geïnjecteerd.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem wordt aangeroepen om een werkitem in de wachtrij te zetten.
  • Het werkitem simuleert een langlopende achtergrondtaak:
    • Er worden drie vertragingen van 5 seconden uitgevoerd (Task.Delay).
    • Een try-catch instructie vangt OperationCanceledException als de taak wordt geannuleerd.
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);
        }
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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 wordt gestart in Program.cs:

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

Asynchrone getimede achtergrondtaak

Met de volgende code wordt een asynchrone achtergrondtaak met een timer gemaakt.

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.
        await DoWork();

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

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

    private async Task DoWork()
    {
        int count = Interlocked.Increment(ref _executionCount);

        // Simulate work
        await Task.Delay(TimeSpan.FromSeconds(2));

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

Aanvullende bronnen

In ASP.NET Core kunnen achtergrondtaken worden geïmplementeerd als gehoste services. Een gehoste service is een klasse met achtergrondtaaklogica waarmee de IHostedService interface wordt geïmplementeerd. Dit artikel bevat drie voorbeelden van gehoste services:

  • Achtergrondtaak die wordt uitgevoerd op een timer.
  • Gehoste service waarmee een scoped service wordt geactiveerd. De scoped service kan afhankelijkheidsinjectie (DI) gebruiken.
  • Achtergrondtaken in wachtrij die opeenvolgend worden uitgevoerd.

Voorbeeldcode bekijken of downloaden (hoe download je)

Worker Service-sjabloon

De ASP.NET Core Worker Service-sjabloon biedt een startpunt voor het schrijven van langlopende service-apps. Een app die is gemaakt op basis van de Worker Service-sjabloon geeft de Worker SDK in het bijbehorende projectbestand op:

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

De sjabloon gebruiken als basis voor een gehoste services-app:

  1. Maak een nieuw project.
  2. Selecteer Worker Service. Kies Volgende.
  3. Geef een projectnaam op in het veld Projectnaam of accepteer de standaardprojectnaam. Klik op Creëren.
  4. Selecteer in het dialoogvenster Een nieuwe werkservice makenmaken.

Package

Een app op basis van de Worker Service-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het Microsoft.Extensions.Hosting-pakket . Zie bijvoorbeeld het projectbestand van de voorbeeld-app (BackgroundTasksSample.csproj).

Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet verwezen naar het pakket Microsoft.Extensions.Hosting vanuit het gedeelde framework. Een expliciete pakketreferentie in het projectbestand van de app is niet vereist.

IHostedService-interface

De IHostedService interface definieert twee methoden voor objecten die worden beheerd door de host:

StartAsync

StartAsync bevat de logica voor het starten van de achtergrondtaak. StartAsync wordt eerder aangeroepen:

Het standaardgedrag kan worden gewijzigd, zodat de gehoste service StartAsync wordt uitgevoerd nadat de pijplijn van de app is geconfigureerd en ApplicationStarted wordt aangeroepen. Als u het standaardgedrag wilt wijzigen, voegt u de gehoste service (VideosWatcher in het volgende voorbeeld) toe na het aanroepen 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

Het annuleringstoken heeft een standaard time-out van vijf seconden om aan te geven dat het afsluitproces niet langer soepel zal verlopen. Wanneer annulering wordt aangevraagd op het token:

  • Alle resterende achtergrondbewerkingen die door de app worden uitgevoerd, moeten worden afgebroken.
  • Alle methoden die binnen StopAsync worden aangeroepen, moeten hun resultaat onmiddellijk teruggeven.

Taken worden echter niet stopgezet nadat annulering is aangevraagd, de oproeper wacht tot alle taken zijn voltooid.

Als de app onverwacht wordt afgesloten (bijvoorbeeld wanneer het proces van de app mislukt), wordt StopAsync mogelijk niet aangeroepen. Daarom kunnen methoden die worden aangeroepen of bewerkingen die in StopAsync worden uitgevoerd, mogelijk niet plaatsvinden.

Als u de standaard time-out van vijf seconden voor afsluiten wilt verlengen, stelt u het volgende in:

De gehoste service wordt eenmaal geactiveerd bij het opstarten van de app en sluit deze probleemloos af bij het afsluiten van de app. Als er een fout optreedt tijdens het uitvoeren van de achtergrondtaak, moet Dispose worden aangeroepen, zelfs als StopAsync niet wordt aangeroepen.

Basisklasse BackgroundService

BackgroundService is een basisklasse voor het implementeren van een langlopende IHostedService.

ExecuteAsync(CancellationToken) wordt aangeroepen om de achtergrondservice uit te voeren. De implementatie retourneert een Task waarde die de volledige levensduur van de achtergrondservice vertegenwoordigt. Er worden geen verdere services gestart totdat ExecuteAsync asynchroon wordt, zoals door aan te roepen await. Vermijd het uitvoeren van lange, blokkerende initialisatiewerkzaamheden in ExecuteAsync. De hostblokken in StopAsync(CancellationToken) wachten op het voltooien van ExecuteAsync.

Het annuleringstoken wordt geactiveerd wanneer IHostedService.StopAsync wordt aangeroepen. Uw implementatie van ExecuteAsync moet onmiddellijk worden voltooid wanneer het annuleringstoken wordt geactiveerd om de service probleemloos af te sluiten. Anders wordt de service op een abrupte manier afgesloten bij het verstrijken van de time-out voor afsluiten. Zie de interfacesectie IHostedService voor meer informatie.

StartAsync moet worden beperkt tot kortlopende taken, omdat gehoste services opeenvolgend worden uitgevoerd en er geen verdere services worden gestart totdat StartAsync de uitvoering is voltooid. Langlopende taken moeten worden geplaatst in ExecuteAsync. Zie de bron voor BackgroundService voor meer informatie.

Getimede achtergrondtaken

Een getimede achtergrondtaak maakt gebruik van de klasse System.Threading.Timer . De timer activeert de taakmethode van DoWork. De timer is uitgeschakeld StopAsync en verwijderd wanneer de servicecontainer wordt verwijderd op 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();
    }
}

Timer wacht niet tot eerdere uitvoeringen van DoWork zijn voltooid, dus de weergegeven benadering is mogelijk niet geschikt voor elk scenario. Interlocked.Increment wordt gebruikt om de uitvoeringsteller als een atomische bewerking te verhogen, waardoor meerdere threads niet gelijktijdig executionCount bijwerken.

De service is geregistreerd in IHostBuilder.ConfigureServices (Program.cs) met de AddHostedService uitbreidingsmethode.

services.AddHostedService<TimedHostedService>();

Een scoped service gebruiken in een achtergrondtaak

Om scoped services binnen een BackgroundService te gebruiken, creëert u een scope. Standaard wordt er geen scope gecreëerd voor een gehoste service.

De service voor achtergrondtaken met beperkte reikwijdte bevat de logica van de achtergrondtaak. In het volgende voorbeeld:

  • De dienst is asynchroon. De DoWork methode retourneert een Task. Voor demonstratiedoeleinden wordt een vertraging van tien seconden in de DoWork methode verwacht.
  • Een ILogger wordt in de service geïnjecteerd.
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);
        }
    }
}

De gehoste service creëert een scope om de gescopeerde achtergrondtaak dienst op te lossen om de DoWork methode aan te roepen. DoWork geeft als resultaat een Task, die in ExecuteAsync wordt afgewacht.

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);
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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

Achtergrondtaken in wachtrij

Een achtergrondtaakwachtrij is gebaseerd op .NET Framework 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;
    }
}

In het volgende QueueHostedService voorbeeld:

  • De BackgroundProcessing-methode geeft een Task terug, die wordt afgewacht in ExecuteAsync.
  • Achtergrondtaken in de wachtrij worden uit de wachtrij gehaald en uitgevoerd in BackgroundProcessing.
  • Werkitems worden gewacht voordat de service stopt.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);
    }
}

Een MonitorLoop service behandelt het in de rij plaatsen van taken voor de gehoste service wanneer de w toets wordt ingedrukt op een invoerapparaat.

  • De IBackgroundTaskQueue wordt in de MonitorLoop service geïnjecteerd.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem wordt aangeroepen om een werkitem in de wachtrij te zetten.
  • Het werkitem simuleert een langlopende achtergrondtaak:
    • Er worden drie vertragingen van 5 seconden uitgevoerd (Task.Delay).
    • Een try-catch instructie vangt OperationCanceledException als de taak wordt geannuleerd.
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);
        }
    }
}

De services worden geregistreerd in IHostBuilder.ConfigureServices (Program.cs). De gehoste service wordt geregistreerd bij de AddHostedService extensiemethode:

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 wordt gestart in Program.Main:

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

Aanvullende bronnen