Dela via


Bakgrundsaktiviteter med värdbaserade tjänster i ASP.NET Core

Av Jeow Li Huan

Anmärkning

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Viktigt!

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

I ASP.NET Core kan bakgrundsaktiviteter implementeras som värdbaserade tjänster. En värdbaserad tjänst är en klass med bakgrundsaktivitetslogik som implementerar IHostedService gränssnittet. Den här artikeln innehåller tre exempel på värdbaserade tjänster:

  • Bakgrundsaktivitet som körs på en tidtagare.
  • Värdbaserad tjänst som aktiverar en begränsad tjänst. Den begränsade tjänsten kan använda beroendeinmatning (DI).
  • Köade bakgrundsaktiviteter som körs sekventiellt.

Arbetarstjänstmall

Mallen ASP.NET Core Worker Service är en startpunkt för att skriva tidskrävande tjänstappar. En app som skapats från Worker Service-mallen anger Worker SDK i sin projektfil:

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

Så här använder du mallen som grund för en värdbaserad tjänstapp:

  1. Skapa ett nytt projekt.
  2. Välj Worker Service. Välj Nästa.
  3. Ange ett projektnamn i fältet Projektnamn eller godkänn standardprojektets namn. Välj Nästa.
  4. I dialogrutan Ytterligare information väljer du ett ramverk. Välj Skapa.

Paket

En app som baseras på Worker Service-mallen Microsoft.NET.Sdk.Worker använder SDK:t och har en explicit paketreferens till Microsoft.Extensions.Hosting-paketet . Se till exempel exempelappens projektfil (BackgroundTasksSample.csproj).

För webbappar som använder Microsoft.NET.Sdk.Web SDK refereras Microsoft.Extensions.Hosting-paketet implicit från det delade ramverket. En explicit paketreferens i appens projektfil krävs inte.

IHostedService-gränssnitt

Gränssnittet IHostedService definierar två metoder för objekt som hanteras av värden:

StartAsync

StartAsync(CancellationToken) innehåller logiken för att starta bakgrundsaktiviteten. StartAsync kallas innan:

StartAsync bör begränsas till kortvariga uppgifter eftersom värdbaserade tjänster körs sekventiellt och inga ytterligare tjänster startas förrän körningarna har slutförts StartAsync .

StopAsync

Annulleringstoken har en standardtidsgräns på 30 sekunder för att indikera att avstängningsprocessen inte längre ska vara mjuk. När annullering begärs för token:

  • Eventuella återstående bakgrundsåtgärder som appen utför bör avbrytas.
  • Alla metoder som anropas StopAsync bör returneras omedelbart.

Aktiviteter avbryts dock inte när annulleringen har begärts – anroparen väntar på att alla aktiviteter ska slutföras.

Om appen stängs av oväntat (till exempel misslyckas appens process) StopAsync kanske den inte anropas. Därför kanske inte några metoder som anropas eller åtgärder som utförs i StopAsync inträffar.

Om du vill utöka standardgränsen på 30 sekunder för avstängning anger du:

Den värdbaserade tjänsten aktiveras en gång vid appstart och stängs av på ett smidigt sätt vid appavstängning. Om ett fel utlöses under körning av bakgrundsaktiviteter ska Dispose anropas, även om StopAsync inte anropas.

BackgroundService-basklass

BackgroundService är en basklass för att implementera en långvarig IHostedService.

ExecuteAsync(CancellationToken) anropas för att köra bakgrundstjänsten. Implementeringen returnerar en Task som representerar bakgrundstjänstens hela livslängd. Inga ytterligare tjänster startas förrän ExecuteAsync blir asynkront, till exempel genom att anropa await. Undvik att utföra långa, blockerande initialiseringsarbete i ExecuteAsync. Värden blockerar StopAsync(CancellationToken) medan den väntar på att ExecuteAsync ska slutföras.

Annulleringstoken utlöses när IHostedService.StopAsync anropas. Implementeringen av ExecuteAsync bör slutföras omedelbart när annulleringstoken utlöses för att kunna stänga av tjänsten på ett korrekt sätt. Annars stängs tjänsten okontrollerat av vid avstängningens tidsgräns. Mer information finns i avsnittet IHostedService-gränssnitt .

Mer information finns i BackgroundService-källkoden .

Tidsåtgång för bakgrundsaktiviteter

En tidsinställd bakgrundsaktivitet använder klassen System.Threading.Timer . Timern utlöser aktivitetens DoWork metod. Timern inaktiveras på StopAsync och tas bort när tjänstcontainern tas bort på 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 väntar inte på att tidigare körningar av DoWork ska slutföras, därför kanske den visade metoden inte är lämplig för varje scenario. Interlocked.Increment används för att öka körningsräknaren som en atomisk åtgärd, vilket säkerställer att flera trådar inte uppdateras executionCount samtidigt.

Tjänsten är registrerad i IHostBuilder.ConfigureServices (Program.cs) med AddHostedService tilläggsmetoden:

services.AddHostedService<TimedHostedService>();

Använda en avgränsad service i en bakgrundsuppgift

Om du vill använda begränsade tjänster inom en BackgroundService skapar du ett omfång. Inget omfång skapas för en värdbaserad tjänst som standard.

Tjänsten för begränsade bakgrundsaktiviteter innehåller bakgrundsaktivitetens logik. I följande exempel:

  • Tjänsten är asynkron. Metoden DoWork returnerar en Task. I demonstrationssyfte väntar en fördröjning på tio sekunder i DoWork metoden.
  • En ILogger matas in i tjänsten.
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);
        }
    }
}

Den värdbaserade tjänsten skapar ett omfång för att lösa den begränsade bakgrundsaktivitetstjänsten så att den kan anropa sin DoWork metod. DoWork returnerar en Task, som väntas i 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);
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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

Bakgrundsaktiviteter i kö

En bakgrundsaktivitetskö baseras på .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;
    }
}

I följande QueueHostedService exempel:

  • Metoden BackgroundProcessing returnerar en Task, som väntar i ExecuteAsync.
  • Bakgrundsuppgifter i kön tas bort och körs i BackgroundProcessing.
  • Arbetsobjekt väntar innan tjänsten stoppas i 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);
    }
}

En MonitorLoop tjänst hanterar lagringsuppgifter för den värdbaserade tjänsten när w nyckeln väljs på en indataenhet:

  • IBackgroundTaskQueue injiceras i MonitorLoop-tjänsten.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem anropas för att lägga till ett arbetsobjekt i kön.
  • Arbetsobjektet simulerar en långvarig bakgrundsaktivitet:
    • Tre fördröjningar utförs på 5 sekunder vardera (Task.Delay).
    • En try-catch sats fångar OperationCanceledException om aktiviteten avbryts.
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);
        }
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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 startas i Program.cs:

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

Asynkron tidsinställd bakgrundsaktivitet

Följande kod skapar en asynkron tidsinställd bakgrundsaktivitet:

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

Inbyggd AOT

Worker Service-mallarna har stöd för .NET native ahead-of-time (AOT) med --aot flaggan:

  1. Skapa ett nytt projekt.
  2. Välj Worker Service. Välj Nästa.
  3. Ange ett projektnamn i fältet Projektnamn eller godkänn standardprojektets namn. Välj Nästa.
  4. I dialogrutan Ytterligare information :
  5. Välj ett ramverk.
  6. Markera kryssrutan Aktivera intern AOT-publicering .
  7. Välj Skapa.

AOT-alternativet lägger <PublishAot>true</PublishAot> till i projektfilen:


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

Ytterligare resurser

I ASP.NET Core kan bakgrundsaktiviteter implementeras som värdbaserade tjänster. En värdbaserad tjänst är en klass med bakgrundsaktivitetslogik som implementerar IHostedService gränssnittet. Den här artikeln innehåller tre exempel på värdbaserade tjänster:

  • Bakgrundsaktivitet som körs på en tidtagare.
  • Värdbaserad tjänst som aktiverar en begränsad tjänst. Den begränsade tjänsten kan använda beroendeinmatning (DI).
  • Köade bakgrundsaktiviteter som körs sekventiellt.

Arbetarstjänstmall

Mallen ASP.NET Core Worker Service är en startpunkt för att skriva tidskrävande tjänstappar. En app som skapats från Worker Service-mallen anger Worker SDK i sin projektfil:

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

Så här använder du mallen som grund för en värdbaserad tjänstapp:

  1. Skapa ett nytt projekt.
  2. Välj Worker Service. Välj Nästa.
  3. Ange ett projektnamn i fältet Projektnamn eller godkänn standardprojektets namn. Välj Nästa.
  4. I dialogrutan Ytterligare information väljer du ett ramverk. Välj Skapa.

Paket

En app som baseras på Worker Service-mallen Microsoft.NET.Sdk.Worker använder SDK:t och har en explicit paketreferens till Microsoft.Extensions.Hosting-paketet . Se till exempel exempelappens projektfil (BackgroundTasksSample.csproj).

För webbappar som använder Microsoft.NET.Sdk.Web SDK refereras Microsoft.Extensions.Hosting-paketet implicit från det delade ramverket. En explicit paketreferens i appens projektfil krävs inte.

IHostedService-gränssnitt

Gränssnittet IHostedService definierar två metoder för objekt som hanteras av värden:

StartAsync

StartAsync(CancellationToken) innehåller logiken för att starta bakgrundsaktiviteten. StartAsync kallas innan:

StartAsync bör begränsas till kortvariga uppgifter eftersom värdbaserade tjänster körs sekventiellt och inga ytterligare tjänster startas förrän körningarna har slutförts StartAsync .

StopAsync

Annulleringstoken har en standardtidsgräns på 30 sekunder för att indikera att avstängningsprocessen inte längre ska vara mjuk. När annullering begärs för token:

  • Eventuella återstående bakgrundsåtgärder som appen utför bör avbrytas.
  • Alla metoder som anropas StopAsync bör returneras omedelbart.

Aktiviteter avbryts dock inte när annulleringen har begärts – anroparen väntar på att alla aktiviteter ska slutföras.

Om appen stängs av oväntat (till exempel misslyckas appens process) StopAsync kanske den inte anropas. Därför kanske inte några metoder som anropas eller åtgärder som utförs i StopAsync inträffar.

Om du vill utöka standardgränsen på 30 sekunder för avstängning anger du:

Den värdbaserade tjänsten aktiveras en gång vid appstart och stängs av på ett smidigt sätt vid appavstängning. Om ett fel utlöses under körning av bakgrundsaktiviteter ska Dispose anropas, även om StopAsync inte anropas.

BackgroundService-basklass

BackgroundService är en basklass för att implementera en långvarig IHostedService.

ExecuteAsync(CancellationToken) anropas för att köra bakgrundstjänsten. Implementeringen returnerar en Task som representerar bakgrundstjänstens hela livslängd. Inga ytterligare tjänster startas förrän ExecuteAsync blir asynkront, till exempel genom att anropa await. Undvik att utföra långa, blockerande initialiseringsarbete i ExecuteAsync. Värden blockerar StopAsync(CancellationToken) medan den väntar på att ExecuteAsync ska slutföras.

Annulleringstoken utlöses när IHostedService.StopAsync anropas. Implementeringen av ExecuteAsync bör slutföras omedelbart när annulleringstoken utlöses för att kunna stänga av tjänsten på ett korrekt sätt. Annars stängs tjänsten okontrollerat av vid avstängningens tidsgräns. Mer information finns i avsnittet IHostedService-gränssnitt .

Mer information finns i BackgroundService-källkoden .

Tidsåtgång för bakgrundsaktiviteter

En tidsinställd bakgrundsaktivitet använder klassen System.Threading.Timer . Timern utlöser aktivitetens DoWork metod. Timern inaktiveras på StopAsync och tas bort när tjänstcontainern tas bort på 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 väntar inte på att tidigare körningar av DoWork ska slutföras, därför kanske den visade metoden inte är lämplig för varje scenario. Interlocked.Increment används för att öka körningsräknaren som en atomisk åtgärd, vilket säkerställer att flera trådar inte uppdateras executionCount samtidigt.

Tjänsten är registrerad i IHostBuilder.ConfigureServices (Program.cs) med AddHostedService tilläggsmetoden:

services.AddHostedService<TimedHostedService>();

Använda en avgränsad service i en bakgrundsuppgift

Om du vill använda begränsade tjänster inom en BackgroundService skapar du ett omfång. Inget omfång skapas för en värdbaserad tjänst som standard.

Tjänsten för begränsade bakgrundsaktiviteter innehåller bakgrundsaktivitetens logik. I följande exempel:

  • Tjänsten är asynkron. Metoden DoWork returnerar en Task. I demonstrationssyfte väntar en fördröjning på tio sekunder i DoWork metoden.
  • En ILogger matas in i tjänsten.
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);
        }
    }
}

Den värdbaserade tjänsten skapar ett omfång för att lösa den begränsade bakgrundsaktivitetstjänsten så att den kan anropa sin DoWork metod. DoWork returnerar en Task, som väntas i 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);
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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

Bakgrundsaktiviteter i kö

En bakgrundsaktivitetskö baseras på .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;
    }
}

I följande QueueHostedService exempel:

  • Metoden BackgroundProcessing returnerar en Task, som väntar i ExecuteAsync.
  • Bakgrundsuppgifter i kön tas bort och körs i BackgroundProcessing.
  • Arbetsobjekt väntar innan tjänsten stoppas i 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);
    }
}

En MonitorLoop tjänst hanterar lagringsuppgifter för den värdbaserade tjänsten när w nyckeln väljs på en indataenhet:

  • IBackgroundTaskQueue injiceras i MonitorLoop-tjänsten.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem anropas för att lägga till ett arbetsobjekt i kön.
  • Arbetsobjektet simulerar en långvarig bakgrundsaktivitet:
    • Tre fördröjningar utförs på 5 sekunder vardera (Task.Delay).
    • En try-catch sats fångar OperationCanceledException om aktiviteten avbryts.
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);
        }
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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 startas i Program.cs:

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

Asynkron tidsinställd bakgrundsaktivitet

Följande kod skapar en asynkron tidsinställd bakgrundsaktivitet:

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

Ytterligare resurser

I ASP.NET Core kan bakgrundsaktiviteter implementeras som värdbaserade tjänster. En värdbaserad tjänst är en klass med bakgrundsaktivitetslogik som implementerar IHostedService gränssnittet. Den här artikeln innehåller tre exempel på värdbaserade tjänster:

  • Bakgrundsaktivitet som körs på en tidtagare.
  • Värdbaserad tjänst som aktiverar en begränsad tjänst. Den begränsade tjänsten kan använda beroendeinmatning (DI).
  • Köade bakgrundsaktiviteter som körs sekventiellt.

Visa eller ladda ned exempelkod (hur du laddar ned)

Arbetarstjänstmall

Mallen ASP.NET Core Worker Service är en startpunkt för att skriva tidskrävande tjänstappar. En app som skapats från Worker Service-mallen anger Worker SDK i sin projektfil:

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

Så här använder du mallen som grund för en värdbaserad tjänstapp:

  1. Skapa ett nytt projekt.
  2. Välj Worker Service. Välj Nästa.
  3. Ange ett projektnamn i fältet Projektnamn eller godkänn standardprojektets namn. Välj Skapa.
  4. I dialogrutan Skapa en ny Arbetstjänst väljer du Skapa.

Paket

En app som baseras på Worker Service-mallen Microsoft.NET.Sdk.Worker använder SDK:t och har en explicit paketreferens till Microsoft.Extensions.Hosting-paketet . Se till exempel exempelappens projektfil (BackgroundTasksSample.csproj).

För webbappar som använder Microsoft.NET.Sdk.Web SDK refereras Microsoft.Extensions.Hosting-paketet implicit från det delade ramverket. En explicit paketreferens i appens projektfil krävs inte.

IHostedService-gränssnitt

Gränssnittet IHostedService definierar två metoder för objekt som hanteras av värden:

StartAsync

StartAsync innehåller logiken för att starta bakgrundsaktiviteten. StartAsync kallas innan:

Standardbeteendet kan ändras så att den värdbaserade tjänsten körs StartAsync efter att appens pipeline har konfigurerats och ApplicationStarted anropas. Om du vill ändra standardbeteendet lägger du till den värdbaserade tjänsten (VideosWatcher i följande exempel) efter att du har anropat 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

Annulleringstokenet har en fem sekunders standardtimeout som anger att avstängningsprocessen inte längre ska vara smidig. När annullering begärs för token:

  • Eventuella återstående bakgrundsåtgärder som appen utför bör avbrytas.
  • Alla metoder som anropas StopAsync bör returneras omedelbart.

Aktiviteter avbryts dock inte när annulleringen har begärts – anroparen väntar på att alla aktiviteter ska slutföras.

Om appen stängs av oväntat (till exempel misslyckas appens process) StopAsync kanske den inte anropas. Därför kanske inte några metoder som anropas eller åtgärder som utförs i StopAsync inträffar.

Om du vill utöka tidsgränsen för fem sekunders standardavstängning anger du:

Den värdbaserade tjänsten aktiveras en gång vid appstart och stängs av på ett smidigt sätt vid appavstängning. Om ett fel utlöses under körning av bakgrundsaktiviteter ska Dispose anropas, även om StopAsync inte anropas.

BackgroundService-basklass

BackgroundService är en basklass för att implementera en långvarig IHostedService.

ExecuteAsync(CancellationToken) anropas för att köra bakgrundstjänsten. Implementeringen returnerar en Task som representerar bakgrundstjänstens hela livslängd. Inga ytterligare tjänster startas förrän ExecuteAsync blir asynkront, till exempel genom att anropa await. Undvik att utföra långa, blockerande initialiseringsarbete i ExecuteAsync. Värden blockerar StopAsync(CancellationToken) medan den väntar på att ExecuteAsync ska slutföras.

Annulleringstoken utlöses när IHostedService.StopAsync anropas. Implementeringen av ExecuteAsync bör slutföras omedelbart när annulleringstoken utlöses för att kunna stänga av tjänsten på ett korrekt sätt. Annars stängs tjänsten okontrollerat av vid avstängningens tidsgräns. Mer information finns i avsnittet IHostedService-gränssnitt .

StartAsync bör begränsas till kortvariga uppgifter eftersom värdbaserade tjänster körs sekventiellt och inga ytterligare tjänster startas förrän körningarna har slutförts StartAsync . Tidskrävande uppgifter ska placeras i ExecuteAsync. Mer information finns i källan till BackgroundService.

Tidsåtgång för bakgrundsaktiviteter

En tidsinställd bakgrundsaktivitet använder klassen System.Threading.Timer . Timern utlöser aktivitetens DoWork metod. Timern inaktiveras på StopAsync och tas bort när tjänstcontainern tas bort på 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 väntar inte på att tidigare körningar av DoWork ska slutföras, därför kanske den visade metoden inte är lämplig för varje scenario. Interlocked.Increment används för att öka körningsräknaren som en atomisk åtgärd, vilket säkerställer att flera trådar inte uppdateras executionCount samtidigt.

Tjänsten är registrerad i IHostBuilder.ConfigureServices (Program.cs) med AddHostedService tilläggsmetoden:

services.AddHostedService<TimedHostedService>();

Använda en avgränsad service i en bakgrundsuppgift

Om du vill använda begränsade tjänster inom en BackgroundService skapar du ett omfång. Inget omfång skapas för en värdbaserad tjänst som standard.

Tjänsten för begränsade bakgrundsaktiviteter innehåller bakgrundsaktivitetens logik. I följande exempel:

  • Tjänsten är asynkron. Metoden DoWork returnerar en Task. I demonstrationssyfte väntar en fördröjning på tio sekunder i DoWork metoden.
  • En ILogger matas in i tjänsten.
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);
        }
    }
}

Den värdbaserade tjänsten skapar ett omfång för att lösa den begränsade bakgrundsaktivitetstjänsten så att den kan anropa sin DoWork metod. DoWork returnerar en Task, som väntas i 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);
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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

Bakgrundsaktiviteter i kö

En bakgrundsaktivitetskö baseras på .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;
    }
}

I följande QueueHostedService exempel:

  • Metoden BackgroundProcessing returnerar en Task, som väntar i ExecuteAsync.
  • Bakgrundsuppgifter i kön tas bort och körs i BackgroundProcessing.
  • Arbetsobjekt väntar innan tjänsten stoppas i 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);
    }
}

En MonitorLoop tjänst hanterar lagringsuppgifter för den värdbaserade tjänsten när w nyckeln väljs på en indataenhet:

  • IBackgroundTaskQueue injiceras i MonitorLoop-tjänsten.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem anropas för att lägga till ett arbetsobjekt i kön.
  • Arbetsobjektet simulerar en långvarig bakgrundsaktivitet:
    • Tre fördröjningar utförs på 5 sekunder vardera (Task.Delay).
    • En try-catch sats fångar OperationCanceledException om aktiviteten avbryts.
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);
        }
    }
}

Tjänsterna är registrerade i IHostBuilder.ConfigureServices (Program.cs). Den värdbaserade tjänsten är registrerad med AddHostedService tilläggsmetoden:

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 startas i Program.Main:

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

Ytterligare resurser