Úlohy na pozadí s hostovanými službami v ASP.NET CoreBackground tasks with hosted services in ASP.NET Core

Jeow li HuanBy Jeow Li Huan

V ASP.NET Core se úlohy na pozadí dají implementovat jako hostované služby.In ASP.NET Core, background tasks can be implemented as hosted services. Hostovaná služba je třída s logikou úlohy na pozadí, která implementuje IHostedService rozhraní.A hosted service is a class with background task logic that implements the IHostedService interface. V tomto tématu najdete tři příklady hostovaných služeb:This topic provides three hosted service examples:

Zobrazit nebo stáhnout ukázkový kód (Jak stáhnout)View or download sample code (how to download)

Šablona služby pracovní procesWorker Service template

Šablona služby ASP.NET Core Worker poskytuje výchozí bod pro psaní dlouhotrvajících aplikací služeb.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Aplikace vytvořená ze šablony pracovní služby určuje sadu SDK pracovního procesu v jeho souboru projektu:An app created from the Worker Service template specifies the Worker SDK in its project file:

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

Použití šablony jako základu pro aplikaci hostované služby:To use the template as a basis for a hosted services app:

  1. Vytvoření nového projektuCreate a new project.
  2. Vyberte položku Pracovní služba.Select Worker Service. Vyberte další.Select Next.
  3. Zadejte název projektu do pole Název projektu nebo přijměte výchozí název projektu.Provide a project name in the Project name field or accept the default project name. Vyberte Vytvořit.Select Create.
  4. V dialogovém okně Vytvořit novou pracovní službu vyberte Vytvořit.In the Create a new Worker service dialog, select Create.

BalíčekPackage

Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker sadu SDK a má explicitní odkaz na balíček na balíček Microsoft. Extensions. hosting .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. Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample. csproj).For example, see the sample app's project file (BackgroundTasksSample.csproj).

U webových aplikací, které používají Microsoft.NET.Sdk.Web sadu SDK, se na balíček Microsoft. Extensions. hosting odkazuje implicitně ze sdíleného rozhraní.For web apps that use the Microsoft.NET.Sdk.Web SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. V souboru projektu aplikace není vyžadován explicitní odkaz na balíček.An explicit package reference in the app's project file isn't required.

Rozhraní IHostedServiceIHostedService interface

IHostedServiceRozhraní definuje dvě metody pro objekty, které jsou spravovány hostitelem:The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync (CancellationToken): StartAsync obsahuje logiku pro spuštění úlohy na pozadí.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. StartAsync se volá před:StartAsync is called before:

    Výchozí chování se dá změnit tak, aby se hostovaná služba StartAsync spouštěla po nakonfigurování kanálu aplikace a ApplicationStarted zavolala se.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. Chcete-li změnit výchozí chování, přidejte hostovanou službu ( VideosWatcher v následujícím příkladu) po volání 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): aktivovaná v případě, že hostitel provádí bezproblémové vypnutí.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync obsahuje logiku pro ukončení úlohy na pozadí.StopAsync contains the logic to end the background task. Implementujte IDisposable a finalizační metody (destruktory) pro uvolnění jakýchkoli nespravovaných prostředků.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Token zrušení má výchozí 5 sekundový časový limit, který označuje, že proces vypnutí by již neměl být řádný.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Když se na tokenu požaduje zrušení:When cancellation is requested on the token:

    • Všechny zbývající operace na pozadí, které aplikace provádí, by měly být přerušeny.Any remaining background operations that the app is performing should be aborted.
    • Všechny metody, které jsou volány v nástroji, StopAsync by měly vracet výzvu.Any methods called in StopAsync should return promptly.

    Úkoly se ale po zrušení žádosti zruší, protože — volající čeká na dokončení všech úkolů.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Pokud se aplikace neočekávaně ukončí (například proces aplikace se nezdařil), StopAsync nemusí být volána.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Proto žádné metody, které jsou volány nebo operací, které jsou prováděny v nástroji, nemusí StopAsync nastat.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Chcete-li zvětšit výchozí časový limit pěti sekund, nastavte:To extend the default five second shutdown timeout, set:

Hostovaná služba se aktivuje při spuštění aplikace a řádně se vypíná při vypnutí aplikace.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Pokud je vyvolána chyba během provádění úlohy na pozadí, Dispose měla by být volána i v případě, že StopAsync není volána.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Základní třída BackgroundServiceBackgroundService base class

BackgroundService je základní třídou pro implementaci dlouhého běhu IHostedService .BackgroundService is a base class for implementing a long running IHostedService.

Metody ExecuteAsync (CancellationToken) se volá za účelem spuštění služby na pozadí.ExecuteAsync(CancellationToken) is called to run the background service. Implementace vrátí Task , která představuje celou dobu života služby na pozadí.The implementation returns a Task that represents the entire lifetime of the background service. Žádné další služby nejsou spuštěny, dokud metody ExecuteAsync nebude asynchronní, například voláním await .No further services are started until ExecuteAsync becomes asynchronous, such as by calling await. Vyhněte se provádění dlouhého a blokujícího inicializačního fungování v ExecuteAsync .Avoid performing long, blocking initialization work in ExecuteAsync. Hostitelské bloky v StopAsync (CancellationToken) čekají na ExecuteAsync dokončení.The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

Token zrušení se aktivuje, když se zavolá IHostedService. StopAsync .The cancellation token is triggered when IHostedService.StopAsync is called. Vaše implementace ExecuteAsync by měla být dokončena okamžitě při vyvolání tokenu zrušení, aby bylo možné službu řádně vypnout.Your implementation of ExecuteAsync should finish promptly when the cancellation token is fired in order to gracefully shut down the service. V opačném případě se služba nekorektně ukončí v časovém limitu vypnutí.Otherwise, the service ungracefully shuts down at the shutdown timeout. Další informace najdete v části rozhraní IHostedService .For more information, see the IHostedService interface section.

Časované úlohy na pozadíTimed background tasks

Úloha s časovým limitem na pozadí využívá třídu System. Threading. Timer .A timed background task makes use of the System.Threading.Timer class. Časovač aktivuje DoWork metodu úkolu.The timer triggers the task's DoWork method. Časovač je zakázán StopAsync a vyřazen při vyřazení kontejneru služby 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)
    {
        var count = Interlocked.Increment(ref executionCount);

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

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

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

        return Task.CompletedTask;
    }

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

Nečeká na Timer Dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Prolínání. přírůstek se používá ke zvýšení čítače spouštění jako atomické operace, což zajistí, že se více vláken současně neaktualizuje executionCount .Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount concurrently.

Služba je registrována v IHostBuilder.ConfigureServices (program.cs) s AddHostedService metodou rozšíření:The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Využívání vymezené služby v úloze na pozadíConsuming a scoped service in a background task

Pokud chcete používat vymezené služby v rámci BackgroundService, vytvořte obor.To use scoped services within a BackgroundService, create a scope. Ve výchozím nastavení není pro hostovanou službu vytvořen žádný obor.No scope is created for a hosted service by default.

Služba úlohy vymezeného na pozadí obsahuje logiku úlohy na pozadí.The scoped background task service contains the background task's logic. V následujícím příkladu:In the following example:

  • Služba je asynchronní.The service is asynchronous. DoWorkMetoda vrátí Task .The DoWork method returns a Task. Pro demonstrační účely je v metodě očekáváno zpoždění deseti sekund DoWork .For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • ILoggerDo služby se vloží.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);
        }
    }
}

Hostovaná služba vytvoří obor pro rozpoznání služby úlohy na pozadí pro volání DoWork metody.The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. DoWork Vrátí Task , který je očekáván v 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 base.StopAsync(stoppingToken);
    }
}

Služby jsou zaregistrované v IHostBuilder.ConfigureServices (program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:The hosted service is registered with the AddHostedService extension method:

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

Úlohy na pozadí zařazené do frontyQueued background tasks

Fronta úloh na pozadí je založena na rozhraní .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;
    }
}

V následujícím QueueHostedService příkladu:In the following QueueHostedService example:

  • BackgroundProcessingMetoda vrátí hodnotu Task , která je očekávána v ExecuteAsync .The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • Úlohy na pozadí ve frontě se odřadí a spustí v BackgroundProcessing .Background tasks in the queue are dequeued and executed in BackgroundProcessing.
  • Pracovní položky jsou očekávány před zastavením služby 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);
    }
}

MonitorLoopSlužba zpracovává enqueuing úlohy pro hostovanou službu vždy, když w je vybraný klíč na vstupním zařízení:A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • IBackgroundTaskQueueJe vložen do MonitorLoop služby.The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem je volána pro zařazení pracovní položky do fronty.IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
  • Pracovní položka simuluje dlouho běžící úlohu na pozadí:The work item simulates a long-running background task:
    • Spustí se tři prodlevy 5 sekund ( Task.Delay ).Three 5-second delays are executed (Task.Delay).
    • try-catchPříkaz zachytávání, OperationCanceledException Pokud je úkol zrušen.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);
                    }
                });
            }
        }
    }
}

Služby jsou zaregistrované v IHostBuilder.ConfigureServices (program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). Hostovaná služba je zaregistrovaná v rámci AddHostedService metody rozšíření:The hosted service is registered with the AddHostedService extension method:

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

MonitorLoop je spuštěn v Program.Main :MonitorLoop is started in Program.Main:

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

V ASP.NET Core se úlohy na pozadí dají implementovat jako hostované služby.In ASP.NET Core, background tasks can be implemented as hosted services. Hostovaná služba je třída s logikou úlohy na pozadí, která implementuje IHostedService rozhraní.A hosted service is a class with background task logic that implements the IHostedService interface. V tomto tématu najdete tři příklady hostovaných služeb:This topic provides three hosted service examples:

Zobrazit nebo stáhnout ukázkový kód (Jak stáhnout)View or download sample code (how to download)

BalíčekPackage

Odkaz na Microsoft. AspNetCore. app Metapackage nebo přidejte odkaz na balíček do balíčku Microsoft. Extensions. hosting .Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

Rozhraní IHostedServiceIHostedService interface

Hostované služby implementují IHostedService rozhraní.Hosted services implement the IHostedService interface. Rozhraní definuje dvě metody pro objekty, které jsou spravovány hostitelem:The interface defines two methods for objects that are managed by the host:

  • StartAsync (CancellationToken): StartAsync obsahuje logiku pro spuštění úlohy na pozadí.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. Při použití webového hostitele StartAsync se volá po spuštění serveru a aktivaci IApplicationLifetime. ApplicationStarted .When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Při použití obecného hostitele StartAsync se před ApplicationStarted aktivací volá.When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync (CancellationToken): aktivovaná v případě, že hostitel provádí bezproblémové vypnutí.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync obsahuje logiku pro ukončení úlohy na pozadí.StopAsync contains the logic to end the background task. Implementujte IDisposable a finalizační metody (destruktory) pro uvolnění jakýchkoli nespravovaných prostředků.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    Token zrušení má výchozí 5 sekundový časový limit, který označuje, že proces vypnutí by již neměl být řádný.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Když se na tokenu požaduje zrušení:When cancellation is requested on the token:

    • Všechny zbývající operace na pozadí, které aplikace provádí, by měly být přerušeny.Any remaining background operations that the app is performing should be aborted.
    • Všechny metody, které jsou volány v nástroji, StopAsync by měly vracet výzvu.Any methods called in StopAsync should return promptly.

    Úkoly se ale po zrušení žádosti zruší, protože — volající čeká na dokončení všech úkolů.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Pokud se aplikace neočekávaně ukončí (například proces aplikace se nezdařil), StopAsync nemusí být volána.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Proto žádné metody, které jsou volány nebo operací, které jsou prováděny v nástroji, nemusí StopAsync nastat.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Chcete-li zvětšit výchozí časový limit pěti sekund, nastavte:To extend the default five second shutdown timeout, set:

Hostovaná služba se aktivuje při spuštění aplikace a řádně se vypíná při vypnutí aplikace.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Pokud je vyvolána chyba během provádění úlohy na pozadí, Dispose měla by být volána i v případě, že StopAsync není volána.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Časované úlohy na pozadíTimed background tasks

Úloha s časovým limitem na pozadí využívá třídu System. Threading. Timer .A timed background task makes use of the System.Threading.Timer class. Časovač aktivuje DoWork metodu úkolu.The timer triggers the task's DoWork method. Časovač je zakázán StopAsync a vyřazen při vyřazení kontejneru služby 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();
    }
}

Nečeká na Timer Dokončení předchozích spuštění DoWork , takže zobrazený přístup nemusí být vhodný pro každý scénář.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario.

Služba je zaregistrovaná v Startup.ConfigureServices s AddHostedService metodou rozšíření:The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Využívání vymezené služby v úloze na pozadíConsuming a scoped service in a background task

Chcete-li použít vymezené služby v rámci IHostedService , vytvořte obor.To use scoped services within an IHostedService, create a scope. Ve výchozím nastavení není pro hostovanou službu vytvořen žádný obor.No scope is created for a hosted service by default.

Služba úlohy vymezeného na pozadí obsahuje logiku úlohy na pozadí.The scoped background task service contains the background task's logic. V následujícím příkladu je vložena ILogger do služby: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.");
    }
}

Hostovaná služba vytvoří obor pro řešení služby úlohy na pozadí, aby volal jeho DoWork metodu: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;
    }
}

Služby jsou zaregistrované v Startup.ConfigureServices .The services are registered in Startup.ConfigureServices. IHostedServiceImplementace je registrována s AddHostedService metodou rozšíření:The IHostedService implementation is registered with the AddHostedService extension method:

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

Úlohy na pozadí zařazené do frontyQueued background tasks

Fronta úloh na pozadí je založena na .NET Framework 4. x QueueBackgroundWorkItem (nezávazně naplánovaná jako integrovaná pro 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;
    }
}

V nástroji se QueueHostedService úlohy na pozadí ve frontě odřadí a spustí jako BackgroundService, což je základní třída pro implementaci dlouhého běhu IHostedService :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.");
    }
}

Služby jsou zaregistrované v Startup.ConfigureServices .The services are registered in Startup.ConfigureServices. IHostedServiceImplementace je registrována s AddHostedService metodou rozšíření:The IHostedService implementation is registered with the AddHostedService extension method:

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

Ve třídě modelu stránky indexu:In the Index page model class:

  • IBackgroundTaskQueueJe vložen do konstruktoru a přiřazen k Queue .The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • IServiceScopeFactoryJe vložený a přiřazený k _serviceScopeFactory .An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. Objekt pro vytváření se používá k vytvoření instancí IServiceScope , které se používají k vytváření služeb v rámci oboru.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Je vytvořen obor, aby bylo možné použít AppDbContext ( vymezenou) aplikaci k zápisu záznamů databáze do IBackgroundTaskQueue služby (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; }

Je-li na stránce indexu vybráno tlačítko Přidat úkol , OnPostAddTask je metoda spuštěna.When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. QueueBackgroundWorkItem je volána pro zařazení pracovní položky do fronty: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();
}

Další zdroje informacíAdditional resources