Hintergrundtasks mit gehosteten Diensten in ASP.NET CoreBackground tasks with hosted services in ASP.NET Core

Von Jeow Li HuanBy Jeow Li Huan

In ASP.NET Core können Hintergrundtasks als gehostete Dienste implementiert werden.In ASP.NET Core, background tasks can be implemented as hosted services. Ein gehosteter Dienst ist eine Klasse mit Logik für Hintergrundaufgaben, die die Schnittstelle IHostedService implementiert.A hosted service is a class with background task logic that implements the IHostedService interface. In diesem Artikel sind drei Beispiel für gehostete Dienste enthalten:This topic provides three hosted service examples:

  • Hintergrundtasks, die auf einem Timer ausgeführt werden.Background task that runs on a timer.
  • Gehostete Dienste, die einen bereichsbezogenen Dienst aktivieren.Hosted service that activates a scoped service. Der bereichsbezogene Dienst kann eine Abhängigkeitsinjektion (Dependency Injection, DI) verwenden.The scoped service can use dependency injection (DI).
  • Hintergrundtasks in der Warteschlange, die sequenziell ausgeführt werden.Queued background tasks that run sequentially.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

Vorlage „Workerdienst“Worker Service template

Die ASP.NET Core-Vorlage „Workerdienst“ dient als Ausgangspunkt für das Schreiben von Dienstanwendungen mit langer Laufzeit.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Eine aus der Workerdienstvorlage erstellte App gibt das Worker SDK in ihrer Projektdatei an:An app created from the Worker Service template specifies the Worker SDK in its project file:

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

Gehen Sie folgendermaßen vor, wenn Sie die Vorlage als Grundlage für eine Hosted Services-App verwenden möchten:To use the template as a basis for a hosted services app:

  1. Erstellen Sie ein neues Projekt.Create a new project.
  2. Wählen Sie Workerdienst aus.Select Worker Service. Klicken Sie auf Weiter.Select Next.
  3. Geben Sie im Feld Projektname einen Projektnamen ein, oder übernehmen Sie den Standardnamen.Provide a project name in the Project name field or accept the default project name. Wählen Sie Erstellen aus.Select Create.
  4. Wählen Sie im Dialogfeld Neuen Workerdienst erstellen Erstellen aus.In the Create a new Worker service dialog, select Create.

PackagePackage

Eine App, die auf der Workerdienstvorlage basiert, verwendet das Microsoft.NET.Sdk.Worker SDK und verfügt über einen expliziten Paketverweis auf das Microsoft.Extensions.Hosting-Paket.An app based on the Worker Service template uses the Microsoft.NET.Sdk.Worker SDK and has an explicit package reference to the Microsoft.Extensions.Hosting package. Sehen Sie sich dazu beispielsweise die Projektdatei der Beispiel-App (BackgroundTasksSample.csproj) an.For example, see the sample app's project file (BackgroundTasksSample.csproj).

Für Web-Apps, die das Microsoft.NET.Sdk.Web SDK verwenden, wird auf das Microsoft.Extensions.Hosting-Paket implizit über das geteilte Framework verwiesen.For web apps that use the Microsoft.NET.Sdk.Web SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. Es ist kein expliziter Paketverweis in der Projektdatei der App erforderlich.An explicit package reference in the app's project file isn't required.

Die IHostedService-SchnittstelleIHostedService interface

Die IHostedService-Schnittstelle definiert zwei Methoden für Objekte, die vom Host verwaltet werden:The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync enthält die Logik zum Starten des Hintergrundtasks.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. StartAsync wird vor folgenden Vorgängen aufgerufen:StartAsync is called before:

    Das Standardverhalten kann so geändert werden, dass der StartAsync-Vorgang des gehosteten Diensts ausgeführt wird, nachdem die Pipeline der App konfiguriert und ApplicationStarted aufgerufen wurde.The default behavior can be changed so that the hosted service's StartAsync runs after the app's pipeline has been configured and ApplicationStarted is called. Um das Standardverhalten zu ändern, fügen Sie den gehosteten Dienst (VideosWatcher im folgenden Beispiel) nach dem Aufruf von ConfigureWebHostDefaults hinzu:To change the default behavior, add the hosted service (VideosWatcher in the following example) after calling 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(CancellationToken): Wird ausgelöst, wenn der Host ein ordnungsgemäßes Herunterfahren ausführt.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync enthält die Logik zum Beenden des Hintergrundtasks.StopAsync contains the logic to end the background task. Implementieren Sie IDisposable und Finalizer (Destruktoren), um nicht verwaltete Ressourcen zu löschen.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Das Abbruchtoken hat standardmäßig ein Zeitlimit von fünf Sekunden, um zu melden, dass der Prozess des Herunterfahrens nicht mehr ordnungsgemäß ausgeführt wird.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Gehen Sie wie folgt vor, wenn ein Abbruch für das Token angefordert wird:When cancellation is requested on the token:

    • Brechen Sie jegliche von der App ausgeführten Hintergrundoperationen ab.Any remaining background operations that the app is performing should be aborted.
    • Jegliche Methoden, die in StopAsync aufgerufen werden, sollten umgehend zurückgegeben werden.Any methods called in StopAsync should return promptly.

    Allerdings werden keine Tasks abgebrochen, wenn der Abbruch angefordert wird. Der Aufrufer wartet, bis alle Tasks abgeschlossen sind.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Wenn die App unerwartet beendet wird (weil der Prozess der App beispielsweise fehlschlägt), wird StopAsync möglicherweise nicht aufgerufen.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Daher werden die in StopAsync aufgerufenen Methoden oder ausgeführten Operationen nicht durchgeführt.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Um das standardmäßig 5-sekündige Timeout beim Herunterfahren zu verlängern, legen Sie folgendes fest:To extend the default five second shutdown timeout, set:

Der gehostete Dienst wird beim Start der App einmal aktiviert und beim Beenden der App wieder ordnungsgemäß heruntergefahren.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Wenn während der Ausführung von Hintergrundtasks ein Fehler ausgelöst wird, sollte Dispose aufgerufen werden, auch wenn StopAsync nicht aufgerufen wird.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

BackgroundService-BasisklasseBackgroundService base class

BackgroundService ist eine Basisklasse zur Implementierung eines IHostedService mit langer Laufzeit.BackgroundService is a base class for implementing a long running IHostedService.

ExecuteAsync (CancellationToken) wird aufgerufen, um den Hintergrunddienst auszuführen.ExecuteAsync(CancellationToken) is called to run the background service. Die Implementierung gibt einen Task zurück, der die gesamte Lebensdauer des Hintergrunddiensts darstellt.The implementation returns a Task that represents the entire lifetime of the background service. Es werden keine weiteren Dienste gestartet, bis ExecuteAsync asynchron wird, etwa durch den Aufruf von await.No further services are started until ExecuteAsync becomes asynchronous, such as by calling await. Vermeiden Sie die Ausführung von langen, blockierenden Initialisierungsarbeiten in ExecuteAsync.Avoid performing long, blocking initialization work in ExecuteAsync. Die Hostblöcke in StopAsync(CancellationToken) warten auf den Abschluss von ExecuteAsync.The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

Das Abbruchtoken wird beim Aufruf von IHostedService.StopAsync ausgelöst.The cancellation token is triggered when IHostedService.StopAsync is called. Ihre Implementierung von ExecuteAsync sollte unverzüglich beendet werden, wenn das Abbruchtoken ausgelöst wird, um den Dienst ordnungsgemäß herunterzufahren.Your implementation of ExecuteAsync should finish promptly when the cancellation token is fired in order to gracefully shut down the service. Andernfalls wird der Dienst beim Erreichen des Timeouts beim Herunterfahren nicht ordnungsgemäß beendet.Otherwise, the service ungracefully shuts down at the shutdown timeout. Weitere Informationen finden Sie im Abschnitt IHostedService-Schnittstelle.For more information, see the IHostedService interface section.

Zeitlich festgelegte HintergrundtasksTimed background tasks

Zeitlich festgelegte Hintergrundtasks verwenden die Klasse System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. Der Timer löst die DoWork-Methode des Tasks aus.The timer triggers the task's DoWork method. Der Timer wird durch StopAsync deaktiviert und freigegeben, wenn der Dienstcontainer durch Dispose freigegeben ist:The timer is disabled on StopAsync and disposed when the service container is disposed on 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 wartet nicht auf den Abschluss vorheriger Ausführungen von DoWork. Die veranschaulichte Vorgehensweise eignet sich also möglicherweise nicht für alle Szenarios.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment wird zum Erhöhen des Ausführungszählers mit einem atomischen Vorgang verwendet, wodurch sichergestellt wird, dass executionCount nicht durch mehrere Threads gleichzeitig aktualisiert wird.Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount concurrently.

Der Dienst wird in IHostBuilder.ConfigureServices (Program.cs) mit der Erweiterungsmethode AddHostedService registriert:The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Verwenden eines bereichsbezogenen Diensts in einem HintergrundtaskConsuming a scoped service in a background task

Erstellen Sie einen Bereich, um bereichsbezogene Dienste in einem BackgroundService zu verwenden.To use scoped services within a BackgroundService, create a scope. Bereiche werden für einen gehosteten Dienst nicht standardmäßig erstellt.No scope is created for a hosted service by default.

Der bereichsbezogene Dienst für Hintergrundtasks enthält die Logik des Hintergrundtasks.The scoped background task service contains the background task's logic. Im folgenden Beispiel:In the following example:

  • Der Dienst ist asynchron.The service is asynchronous. Die DoWork-Methode gibt Task zurück.The DoWork method returns a Task. Zu Demonstrationszwecken wird in der DoWork-Methode eine Verzögerung von zehn Sekunden verwendet.For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • Ein ILogger wird in den Dienst eingefügt.An ILogger is injected into the service.
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);
        }
    }
}

Der gehostete Dienst erstellt einen Bereich, um den bereichsbezogenen Dienst für Hintergrundtasks aufzulösen, damit die DoWork-Methode aufgerufen wird.The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. DoWork gibt einen Task zurück, auf den in ExecuteAsync gewartet wird:DoWork returns a Task, which is awaited in 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 Task.CompletedTask;
    }
}

Die Dienste werden in IHostBuilder.ConfigureServices (Program.cs) registriert.The services are registered in IHostBuilder.ConfigureServices (Program.cs). Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService registriert:The hosted service is registered with the AddHostedService extension method:

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

Hintergrundtasks in der WarteschlangeQueued background tasks

Eine Warteschlange für Hintergrundaufgaben basiert auf dem .NET 4.x QueueBackgroundWorkItem:A background task queue is based on the .NET 4.x QueueBackgroundWorkItem:

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

Im folgenden Beispiel für QueueHostedService gilt:In the following QueueHostedService example:

  • Die BackgroundProcessing-Methode gibt einen Task zurück, auf den in ExecuteAsync gewartet wird.The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • Hintergrundtasks in der Warteschlange werden aus dieser entfernt und in BackgroundProcessing ausgeführt.Background tasks in the queue are dequeued and executed in BackgroundProcessing.
  • Auf Arbeitselemente wird gewartet, bevor der Dienst in StopAsync angehalten wird.Work items are awaited before the service stops in 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);
    }
}

Ein MonitorLoop-Dienst verarbeitet das Einreihen von Tasks in die Warteschlange für den gehosteten Dienst, wenn der w-Schlüssel auf einem Eingabegerät ausgewählt wird:A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • Die IBackgroundTaskQueue wird in den MonitorLoop-Dienst eingefügt.The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem wird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen.IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
  • Das Arbeitselement simuliert eine Hintergrundaufgabe mit langer Ausführungszeit:The work item simulates a long-running background task:
    • Drei 5-Sekunden-Verzögerungen werden ausgeführt (Task.Delay).Three 5-second delays are executed (Task.Delay).
    • Eine try-catch-Anweisung fängt OperationCanceledException auf, wenn der Task abgebrochen wird.A try-catch statement traps OperationCanceledException if the task is cancelled.
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("Monitor Loop is starting.");

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

    public void Monitor()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                _taskQueue.QueueBackgroundWorkItem(async 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);
                    }
                });
            }
        }
    }
}

Die Dienste werden in IHostBuilder.ConfigureServices (Program.cs) registriert.The services are registered in IHostBuilder.ConfigureServices (Program.cs). Der gehostete Dienst wird mit der Erweiterungsmethode AddHostedService registriert:The hosted service is registered with the AddHostedService extension method:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

MonitorLoop wird in Program.Main gestartet:MonitorLoop is started in Program.Main:

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

In ASP.NET Core können Hintergrundtasks als gehostete Dienste implementiert werden.In ASP.NET Core, background tasks can be implemented as hosted services. Ein gehosteter Dienst ist eine Klasse mit Logik für Hintergrundaufgaben, die die Schnittstelle IHostedService implementiert.A hosted service is a class with background task logic that implements the IHostedService interface. In diesem Artikel sind drei Beispiel für gehostete Dienste enthalten:This topic provides three hosted service examples:

  • Hintergrundtasks, die auf einem Timer ausgeführt werden.Background task that runs on a timer.
  • Gehostete Dienste, die einen bereichsbezogenen Dienst aktivieren.Hosted service that activates a scoped service. Der bereichsbezogene Dienst kann eine Abhängigkeitsinjektion (Dependency Injection, DI) verwenden.The scoped service can use dependency injection (DI)
  • Hintergrundtasks in der Warteschlange, die sequenziell ausgeführt werden.Queued background tasks that run sequentially.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

PackagePackage

Verweisen Sie auf das Microsoft.AspNetCore.App-Metapaket, oder fügen Sie einen Paketverweis auf das Paket Microsoft.Extensions.Hosting hinzu.Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

Die IHostedService-SchnittstelleIHostedService interface

Gehostete Dienste implementieren die IHostedService-Schnittstelle.Hosted services implement the IHostedService interface. Die Schnittstelle definiert zwei Methoden für Objekte, die vom Host verwaltet werden:The interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync enthält die Logik zum Starten des Hintergrundtasks.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. Bei Verwendung des Webhosts wird StartAsync aufgerufen, nachdem der Server gestartet und IApplicationLifetime.ApplicationStarted ausgelöst wurde.When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Bei Verwendung des generischen Hosts wird StartAsync aufgerufen, bevor ApplicationStarted ausgelöst wird.When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync(CancellationToken): Wird ausgelöst, wenn der Host ein ordnungsgemäßes Herunterfahren ausführt.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync enthält die Logik zum Beenden des Hintergrundtasks.StopAsync contains the logic to end the background task. Implementieren Sie IDisposable und Finalizer (Destruktoren), um nicht verwaltete Ressourcen zu löschen.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Das Abbruchtoken hat standardmäßig ein Zeitlimit von fünf Sekunden, um zu melden, dass der Prozess des Herunterfahrens nicht mehr ordnungsgemäß ausgeführt wird.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Gehen Sie wie folgt vor, wenn ein Abbruch für das Token angefordert wird:When cancellation is requested on the token:

    • Brechen Sie jegliche von der App ausgeführten Hintergrundoperationen ab.Any remaining background operations that the app is performing should be aborted.
    • Jegliche Methoden, die in StopAsync aufgerufen werden, sollten umgehend zurückgegeben werden.Any methods called in StopAsync should return promptly.

    Allerdings werden keine Tasks abgebrochen, wenn der Abbruch angefordert wird. Der Aufrufer wartet, bis alle Tasks abgeschlossen sind.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Wenn die App unerwartet beendet wird (weil der Prozess der App beispielsweise fehlschlägt), wird StopAsync möglicherweise nicht aufgerufen.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Daher werden die in StopAsync aufgerufenen Methoden oder ausgeführten Operationen nicht durchgeführt.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Um das standardmäßig 5-sekündige Timeout beim Herunterfahren zu verlängern, legen Sie folgendes fest:To extend the default five second shutdown timeout, set:

Der gehostete Dienst wird beim Start der App einmal aktiviert und beim Beenden der App wieder ordnungsgemäß heruntergefahren.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Wenn während der Ausführung von Hintergrundtasks ein Fehler ausgelöst wird, sollte Dispose aufgerufen werden, auch wenn StopAsync nicht aufgerufen wird.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Zeitlich festgelegte HintergrundtasksTimed background tasks

Zeitlich festgelegte Hintergrundtasks verwenden die Klasse System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. Der Timer löst die DoWork-Methode des Tasks aus.The timer triggers the task's DoWork method. Der Timer wird durch StopAsync deaktiviert und freigegeben, wenn der Dienstcontainer durch Dispose freigegeben ist:The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

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

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

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

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

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

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

        return Task.CompletedTask;
    }

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

Timer wartet nicht auf den Abschluss vorheriger Ausführungen von DoWork. Die veranschaulichte Vorgehensweise eignet sich also möglicherweise nicht für alle Szenarios.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario.

Der Dienst wird in Startup.ConfigureServices mit der Erweiterungsmethode AddHostedService registriert:The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Verwenden eines bereichsbezogenen Diensts in einem HintergrundtaskConsuming a scoped service in a background task

Erstellen Sie einen Bereich, um bereichsbezogene Dienste in IHostedService zu verwenden.To use scoped services within an IHostedService, create a scope. Bereiche werden für einen gehosteten Dienst nicht standardmäßig erstellt.No scope is created for a hosted service by default.

Der bereichsbezogene Dienst für Hintergrundtasks enthält die Logik des Hintergrundtasks.The scoped background task service contains the background task's logic. Im folgenden Beispiel wird ILogger in den Dienst eingefügt:In the following example, an ILogger is injected into the service:

internal interface IScopedProcessingService
{
    void DoWork();
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public void DoWork()
    {
        _logger.LogInformation("Scoped Processing Service is working.");
    }
}

Der gehostete Dienst erstellt einen Bereich, um den bereichsbezogenen Dienst für Hintergrundtasks aufzulösen, um die DoWork-Methode aufzurufen:The hosted service creates a scope to resolve the scoped background task service to call its DoWork method:

internal class ConsumeScopedServiceHostedService : IHostedService
{
    private readonly ILogger _logger;

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

    public IServiceProvider Services { get; }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is starting.");

        DoWork();

        return Task.CompletedTask;
    }

    private void DoWork()
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            scopedProcessingService.DoWork();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        return Task.CompletedTask;
    }
}

Die Dienste werden in Startup.ConfigureServices registriert.The services are registered in Startup.ConfigureServices. Der Implementierung IHostedService wird mit der Erweiterungsmethode AddHostedService registriert:The IHostedService implementation is registered with the AddHostedService extension method:

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

Hintergrundtasks in der WarteschlangeQueued background tasks

Eine Warteschlange für Hintergrundtasks basiert auf dem QueueBackgroundWorkItem von .NET Framework 4.x (die Integration in ASP.NET Core ist vorläufig geplant):A background task queue is based on the .NET Framework 4.x QueueBackgroundWorkItem (tentatively scheduled to be built-in for ASP.NET Core):

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

In QueueHostedService werden Hintergrundaufgaben in der Warteschlange aus der Warteschlange entfernt und als BackgroundService, eine Basisklasse zum Bereitstellen von IHostedService mit langer Ausführungszeit, ausgeführt:In QueueHostedService, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService:

public class QueuedHostedService : BackgroundService
{
    private readonly ILogger _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILoggerFactory loggerFactory)
    {
        TaskQueue = taskQueue;
        _logger = loggerFactory.CreateLogger<QueuedHostedService>();
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected async override Task ExecuteAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Queued Hosted Service is starting.");

        while (!cancellationToken.IsCancellationRequested)
        {
            var workItem = await TaskQueue.DequeueAsync(cancellationToken);

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

        _logger.LogInformation("Queued Hosted Service is stopping.");
    }
}

Die Dienste werden in Startup.ConfigureServices registriert.The services are registered in Startup.ConfigureServices. Der Implementierung IHostedService wird mit der Erweiterungsmethode AddHostedService registriert:The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

In der Modellklasse für die Indexseite:In the Index page model class:

  • IBackgroundTaskQueue wird in den Konstruktor eingefügt und Queue zugewiesen.The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • Ein IServiceScopeFactory wird eingefügt und _serviceScopeFactory zugewiesen.An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. Die Zuordnungsinstanz wird verwendet, um Instanzen von IServiceScope zu erstellen. Dieser wird verwendet, um Dienste in einem Bereich zu erstellen.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Ein Bereich wird erstellt, um den AppDbContext der App (einen bereichsbezogenen Dienst) zu verwenden, um Datenbankeinträge in IBackgroundTaskQueue (ein Singletondienst) zu schreiben.A scope is created in order to use the app's AppDbContext (a scoped service) to write database records in the IBackgroundTaskQueue (a singleton service).
public class IndexModel : PageModel
{
    private readonly AppDbContext _db;
    private readonly ILogger _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public IndexModel(AppDbContext db, IBackgroundTaskQueue queue, 
        ILogger<IndexModel> logger, IServiceScopeFactory serviceScopeFactory)
    {
        _db = db;
        _logger = logger;
        Queue = queue;
        _serviceScopeFactory = serviceScopeFactory;
    }

    public IBackgroundTaskQueue Queue { get; }

Wenn auf der Indexseite auf die Schaltfläche Task hinzufügen geklickt wird, wird die OnPostAddTask-Methode ausgeführt.When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. QueueBackgroundWorkItem wird aufgerufen, um ein Arbeitselement in die Warteschlange einzureihen:QueueBackgroundWorkItem is called to enqueue a work item:

public IActionResult OnPostAddTaskAsync()
{
    Queue.QueueBackgroundWorkItem(async token =>
    {
        var guid = Guid.NewGuid().ToString();

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var scopedServices = scope.ServiceProvider;
            var db = scopedServices.GetRequiredService<AppDbContext>();

            for (int delayLoop = 1; delayLoop < 4; delayLoop++)
            {
                try
                {
                    db.Messages.Add(
                        new Message() 
                        { 
                            Text = $"Queued Background Task {guid} has " +
                                $"written a step. {delayLoop}/3"
                        });
                    await db.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, 
                        "An error occurred writing to the " +
                        "database. Error: {Message}", ex.Message);
                }

                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
        }

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

    return RedirectToPage();
}

Zusätzliche RessourcenAdditional resources