Attività in background con servizi ospitati in ASP.NET CoreBackground tasks with hosted services in ASP.NET Core

Di Luke Latham e Jeow li HuanBy Luke Latham and Jeow Li Huan

In ASP.NET Core le attività in background possono essere implementate come servizi ospitati.In ASP.NET Core, background tasks can be implemented as hosted services. Un servizio ospitato è una classe con logica di attività in background che implementa l'interfaccia IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. In questo argomento vengono forniti tre esempi di servizio ospitato:This topic provides three hosted service examples:

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

Modello di servizio di ruolo di lavoroWorker Service template

Il modello di servizio di ruolo di lavoro di ASP.NET Core rappresenta un punto di partenza per la scrittura di app di servizi a esecuzione prolungata.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Un'app creata dal modello del servizio Worker specifica l'SDK del ruolo di lavoro nel file di progetto:An app created from the Worker Service template specifies the Worker SDK in its project file:

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

Per usare il modello come base per un'app di servizi ospitati:To use the template as a basis for a hosted services app:

  1. Creare un nuovo progetto.Create a new project.
  2. Selezionare il servizio Worker.Select Worker Service. Scegliere Avanti.Select Next.
  3. Specificare il nome di un progetto nel campo Nome progetto oppure accettare il nome predefinito.Provide a project name in the Project name field or accept the default project name. Scegliere Crea.Select Create.
  4. Nella finestra di dialogo Crea un nuovo servizio del ruolo di lavoro selezionare Crea.In the Create a new Worker service dialog, select Create.

PacchettoPackage

Un'app basata sul modello del servizio Worker usa l'SDK Microsoft.NET.Sdk.Worker e contiene un riferimento al pacchetto esplicito al pacchetto Microsoft. Extensions. host .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. Ad esempio, vedere il file di progetto dell'app di esempio (BackgroundTasksSample. csproj).For example, see the sample app's project file (BackgroundTasksSample.csproj).

Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web, viene fatto riferimento in modo implicito al pacchetto Microsoft. Extensions. host dal Framework condiviso.For web apps that use the Microsoft.NET.Sdk.Web SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. Un riferimento esplicito al pacchetto nel file di progetto dell'app non è obbligatorio.An explicit package reference in the app's project file isn't required.

Interfaccia IHostedServiceIHostedService interface

L'interfaccia IHostedService definisce due metodi per gli oggetti gestiti dall'host:The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken)StartAsync contiene la logica per avviare l'attività in background.StartAsync(CancellationToken)StartAsync contains the logic to start the background task. StartAsync viene chiamato prima:StartAsync is called before:

    Il comportamento predefinito può essere modificato in modo che l'StartAsync del servizio ospitato venga eseguito dopo la configurazione della pipeline dell'app e che venga chiamato ApplicationStarted.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. Per modificare il comportamento predefinito, aggiungere il servizio ospitato (VideosWatcher nell'esempio seguente) dopo aver chiamato ConfigureWebHostDefaults: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) – Attivato quando l'host sta eseguendo un arresto normale.StopAsync(CancellationToken) – Triggered when the host is performing a graceful shutdown. StopAsync contiene la logica per terminare l'attività in background.StopAsync contains the logic to end the background task. Implementare IDisposable e i finalizzatori (distruttori) per eliminare tutte le risorse non gestite.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Il token di annullamento ha un timeout predefinito di cinque secondi che indica che il processo di arresto non è più normale.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Quando viene richiesto l'annullamento sul token:When cancellation is requested on the token:

    • Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.Any remaining background operations that the app is performing should be aborted.
    • Tutti i metodi eventuali chiamati in StopAsync devono essere completati rapidamente.Any methods called in StopAsync should return promptly.

    Tuttavia, dopo la richiesta di annullamento le attività non vengono abbandonate: il chiamante attende il completamento di tutte le attività.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Se l'app si arresta in modo imprevisto, ad esempio, il processo dell'app ha esito negativo, il metodo StopAsync potrebbe non essere chiamato.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Pertanto è possibile che i metodi chiamati o le operazioni effettuate in StopAsync non vengano eseguiti.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Per estendere il timeout di arresto predefinito di cinque secondi, impostare:To extend the default five second shutdown timeout, set:

Il servizio ospitato viene attivato una volta all'avvio dell'app e arrestato normalmente all'arresto dell'applicazione.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Se viene generato un errore durante l'esecuzione dell'attività in background, deve essere chiamato Dispose anche se StopAsync non viene chiamato.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Classe di base BackgroundServiceBackgroundService base class

BackgroundService è una classe di base per l'implementazione di una IHostedServicecon esecuzione prolungata.BackgroundService is a base class for implementing a long running IHostedService.

ExecuteAsync (CancellationToken) viene chiamato per eseguire il servizio in background.ExecuteAsync(CancellationToken) is called to run the background service. L'implementazione restituisce un Task che rappresenta l'intera durata del servizio in background.The implementation returns a Task that represents the entire lifetime of the background service. Non vengono avviati altri servizi fino a quando ExecuteAsync non diventa asincrono, ad esempio chiamando await.No further services are started until ExecuteAsync becomes asynchronous, such as by calling await. Evitare di eseguire operazioni di inizializzazione lunghe e bloccate in ExecuteAsync.Avoid performing long, blocking initialization work in ExecuteAsync. Blocchi host in StopAsync (CancellationToken) in attesa del completamento ExecuteAsync.The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

Il token di annullamento viene attivato quando viene chiamato IHostedService. StopAsync .The cancellation token is triggered when IHostedService.StopAsync is called. L'implementazione di ExecuteAsync deve terminare tempestivamente quando viene generato il token di annullamento per arrestare correttamente il servizio.Your implementation of ExecuteAsync should finish promptly when the cancellation token is fired in order to gracefully shut down the service. In caso contrario, il servizio si arresta normalmente in corrispondenza del timeout di arresto.Otherwise, the service ungracefully shuts down at the shutdown timeout. Per ulteriori informazioni, vedere la sezione interfaccia IHostedService .For more information, see the IHostedService interface section.

Attività in background programmateTimed background tasks

Un'attività programmata in background utilizza la classe System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. Il timer attiva il metodo DoWork dell'attività.The timer triggers the task's DoWork method. Il timer viene disabilitato con StopAsync ed eliminato quando il contenitore dei servizi è eliminato con Dispose: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)
    {
        executionCount++;

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

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

Il servizio è registrato nel IHostBuilder.ConfigureServices (Program.cs) con il metodo di estensione AddHostedService:The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Utilizzo di un servizio con ambito in un'attività in backgroundConsuming a scoped service in a background task

Per usare i servizi con ambito all'interno di un BackgroundService, creare un ambito.To use scoped services within a BackgroundService, create a scope. Non viene creato automaticamente alcun ambito per un servizio ospitato.No scope is created for a hosted service by default.

Il servizio dell'attività in background con ambito contiene la logica dell'attività in background.The scoped background task service contains the background task's logic. Nell'esempio seguente:In the following example:

  • Il servizio è asincrono.The service is asynchronous. Il metodo DoWork restituisce un tipo Task.The DoWork method returns a Task. A scopo dimostrativo, è atteso un ritardo di dieci secondi nel metodo DoWork.For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • Un ILogger viene inserito nel servizio.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);
        }
    }
}

Il servizio ospitato crea un ambito per risolvere il servizio attività in background con ambito per chiamare il relativo metodo DoWork.The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. DoWork restituisce un Task, atteso in ExecuteAsync: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;
    }
}

I servizi vengono registrati nel IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). Il servizio ospitato viene registrato con il metodo di estensione AddHostedService:The hosted service is registered with the AddHostedService extension method:

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

Attività in background in codaQueued background tasks

Una coda di attività in background si basa su .NET 4. x QueueBackgroundWorkItem (programma provvisoriamente pianificato per essere incorporato per ASP.NET Core):A background task queue is based on the .NET 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;
    }
}

Nell'esempio di QueueHostedService seguente:In the following QueueHostedService example:

  • Il metodo BackgroundProcessing restituisce un Task, atteso in ExecuteAsync.The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • Le attività in background nella coda vengono rimossi dalla coda ed eseguite in BackgroundProcessing.Background tasks in the queue are dequeued and executed in BackgroundProcessing.
  • Gli elementi di lavoro sono in attesa prima che il servizio venga arrestato in StopAsync.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);
    }
}

Un servizio MonitorLoop gestisce le attività di Accodamento per il servizio ospitato ogni volta che viene selezionata la chiave di w in un dispositivo di input:A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • Il IBackgroundTaskQueue viene inserito nel servizio MonitorLoop.The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem viene chiamato per accodare un elemento di lavoro.IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
  • L'elemento di lavoro simula un'attività in background con esecuzione prolungata:The work item simulates a long-running background task:
    • Vengono eseguiti tre ritardi di 5 secondi (Task.Delay).Three 5-second delays are executed (Task.Delay).
    • Un'istruzione try-catch intrappola OperationCanceledException se l'attività è stata annullata.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);
                    }
                });
            }
        }
    }
}

I servizi vengono registrati nel IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). Il servizio ospitato viene registrato con il metodo di estensione AddHostedService:The hosted service is registered with the AddHostedService extension method:

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

In ASP.NET Core le attività in background possono essere implementate come servizi ospitati.In ASP.NET Core, background tasks can be implemented as hosted services. Un servizio ospitato è una classe con logica di attività in background che implementa l'interfaccia IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. In questo argomento vengono forniti tre esempi di servizio ospitato:This topic provides three hosted service examples:

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

PacchettoPackage

Fare riferimento al metapacchetto Microsoft.AspNetCore.App oppure aggiungere un riferimento al pacchetto Microsoft.Extensions.Hosting.Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

Interfaccia IHostedServiceIHostedService interface

I servizi ospitati implementano l'interfaccia IHostedService.Hosted services implement the IHostedService interface. L'interfaccia definisce due metodi riservati agli oggetti gestiti dall'host:The interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken)StartAsync contiene la logica per avviare l'attività in background.StartAsync(CancellationToken)StartAsync contains the logic to start the background task. Quando si usa l'Host Web, StartAsync viene chiamato dopo l'avvio del server e l'attivazione di IApplicationLifetime.ApplicationStarted.When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Quando si usa l'Host generico, StartAsync viene chiamato prima dell'attivazione di ApplicationStarted.When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync(CancellationToken) – Attivato quando l'host sta eseguendo un arresto normale.StopAsync(CancellationToken) – Triggered when the host is performing a graceful shutdown. StopAsync contiene la logica per terminare l'attività in background.StopAsync contains the logic to end the background task. Implementare IDisposable e i finalizzatori (distruttori) per eliminare tutte le risorse non gestite.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Il token di annullamento ha un timeout predefinito di cinque secondi che indica che il processo di arresto non è più normale.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Quando viene richiesto l'annullamento sul token:When cancellation is requested on the token:

    • Tutte le operazioni in background rimanenti che sta eseguendo l'app devono essere interrotte.Any remaining background operations that the app is performing should be aborted.
    • Tutti i metodi eventuali chiamati in StopAsync devono essere completati rapidamente.Any methods called in StopAsync should return promptly.

    Tuttavia, dopo la richiesta di annullamento le attività non vengono abbandonate: il chiamante attende il completamento di tutte le attività.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Se l'app si arresta in modo imprevisto, ad esempio, il processo dell'app ha esito negativo, il metodo StopAsync potrebbe non essere chiamato.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Pertanto è possibile che i metodi chiamati o le operazioni effettuate in StopAsync non vengano eseguiti.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Per estendere il timeout di arresto predefinito di cinque secondi, impostare:To extend the default five second shutdown timeout, set:

Il servizio ospitato viene attivato una volta all'avvio dell'app e arrestato normalmente all'arresto dell'applicazione.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Se viene generato un errore durante l'esecuzione dell'attività in background, deve essere chiamato Dispose anche se StopAsync non viene chiamato.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Attività in background programmateTimed background tasks

Un'attività programmata in background utilizza la classe System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. Il timer attiva il metodo DoWork dell'attività.The timer triggers the task's DoWork method. Il timer viene disabilitato con StopAsync ed eliminato quando il contenitore dei servizi è eliminato con Dispose: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();
    }
}

Il servizio registrato in Startup.ConfigureServices con il metodo di estensione AddHostedService:The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Utilizzo di un servizio con ambito in un'attività in backgroundConsuming a scoped service in a background task

Per usare i servizi con ambito all'interno di un IHostedService, creare un ambito.To use scoped services within an IHostedService, create a scope. Non viene creato automaticamente alcun ambito per un servizio ospitato.No scope is created for a hosted service by default.

Il servizio dell'attività in background con ambito contiene la logica dell'attività in background.The scoped background task service contains the background task's logic. Nell'esempio seguente, ILogger viene inserito nel servizio: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.");
    }
}

Il servizio ospitato crea un ambito per risolvere il servizio dell'attività in background con ambito e chiamare il relativo metodo DoWork: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;
    }
}

I servizi vengono registrati in Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. L'implementazione di IHostedServiceviene registrata con il metodo di estensione AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

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

Attività in background in codaQueued background tasks

Una coda di attività in background si basa su .NET Framework 4. x QueueBackgroundWorkItem (programma provvisoriamente da incorporare per ASP.NET Core):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, le attività in background in coda vengono rimosse dalla coda ed eseguite come BackgroundService, ovvero una classe di base per l'implementazione di un IHostedService a esecuzione prolungata: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.");
    }
}

I servizi vengono registrati in Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. L'implementazione di IHostedServiceviene registrata con il metodo di estensione AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

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

Nella classe modello della pagina di indice:In the Index page model class:

  • IBackgroundTaskQueue viene inserito nel costruttore e assegnato a Queue.The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • Un'interfaccia IServiceScopeFactory viene inserita e assegnata a _serviceScopeFactory.An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. La factory viene usata per creare istanze di IServiceScope, che consente di creare servizi all'interno di un ambito.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Viene creato un ambito per usare la classe AppDbContext dell'app (un servizio con ambito) per scrivere record di database in IBackgroundTaskQueue (un servizio singleton).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; }

Quando si seleziona il pulsante Aggiungi attività nella pagina di indice, viene eseguito il metodo OnPostAddTask.When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. QueueBackgroundWorkItem viene chiamato per accodare un elemento di lavoro: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();
}

Risorse aggiuntiveAdditional resources