Megosztás a következőn keresztül:


Háttérfeladatok az ASP.NET Core-ban üzemeltetett szolgáltatásokkal

Készítette : Jeow Li Huan

Note

Ez nem a cikk legújabb verziója. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.

Warning

A ASP.NET Core ezen verziója már nem támogatott. További információt a .NET és a .NET Core támogatási szabályzatában talál. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.

A ASP.NET Core-ban a háttérfeladatok üzemeltetett szolgáltatásként implementálhatók. Az üzemeltetett szolgáltatás egy háttérfeladatlogikával rendelkező osztály, amely implementálja a IHostedService felületet. Ez a cikk három üzemeltetett szolgáltatás-példát tartalmaz:

Munkás szolgáltatás sablon

A ASP.NET Core Worker Service-sablon kiindulópontként szolgál a hosszú ideig futó szolgáltatásalkalmazások írásához. A Worker Service-sablonból létrehozott alkalmazás megadja a Worker SDK-t a projektfájlban:

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

Üzemeltetett szolgáltatási alkalmazás alapjaként sablont használni:

  1. Új projekt létrehozása.
  2. Válassza a Munkás szolgáltatáslehetőséget. Válassza a Következőlehetőséget.
  3. Adjon meg egy projektnevet a Projektnév mezőben, vagy fogadja el az alapértelmezett projektnevet. Válassza a Következőlehetőséget.
  4. A További információ párbeszédpanelen válasszon keretrendszert. Válassza a Create gombot.

Package

A Worker Service-sablonon alapuló alkalmazások az SDK-t Microsoft.NET.Sdk.Worker használják, és kifejezetten hivatkoznak a Microsoft.Extensions.Hosting csomagra. Lásd például a mintaalkalmazás projektfájlját (BackgroundTasksSample.csproj).

Az SDK-t használó Microsoft.NET.Sdk.Web webalkalmazások esetében a Microsoft.Extensions.Hosting csomagra implicit módon hivatkozik a megosztott keretrendszer. Az alkalmazás projektfájljában nincs szükség explicit csomaghivatkozásra.

IHostedService felület

Az IHostedService interfész két metódust határoz meg a gazda által kezelt objektumokhoz.

StartAsync

A StartAsync (CancellationToken) tartalmazza a háttérfeladat indítására vonatkozó logikát. StartAsync a kezdet előtt van meghívva:

StartAsync rövid ideig futó tevékenységekre kell korlátozódnia, mert az üzemeltetett szolgáltatások egymás után futnak, és a futtatások befejezéséig StartAsync nem indulnak el további szolgáltatások.

StopAsync

A megszakítási token alapértelmezett 30 másodperces időkorlátot alkalmaz annak jelzésére, hogy a leállítási folyamat már nem folytatódhat zökkenőmentesen. Amikor lemondást kérnek a tokenre:

  • Az alkalmazás által végrehajtott többi háttérműveletet le kell szakítani.
  • Minden behívott StopAsync metódusnak azonnal vissza kell térnie.

A lemondás kérése után azonban a feladatok nem lesznek félbehagyva – a hívó minden feladat befejezésére vár.

Ha az alkalmazás váratlanul leáll (például az alkalmazás folyamata meghiúsul), StopAsync lehet, hogy nem lesz meghívva. Ezért előfordulhat, hogy az StopAsync meghívott metódusok vagy végrehajtott műveletek nem valósulnak meg.

Az alapértelmezett 30 másodperces leállítási időtúllépés meghosszabbításához állítsa be a következőt:

Az üzemeltetett szolgáltatás az alkalmazás indításakor egyszer aktiválódik, és az alkalmazás leállításakor kecsesen leáll. Ha a háttérben futó feladat végrehajtása során hibaüzenet jelenik meg, akkor is meg kell hívni, Dispose ha StopAsync nincs meghívva.

BackgroundService alaposztály

BackgroundService egy alaposztály egy hosszú ideig futó IHostedServiceimplementáláshoz.

Az ExecuteAsync (CancellationToken) parancs a háttérszolgáltatás futtatására van meghívva. Az implementáció egy olyan értéket Task ad vissza, amely a háttérszolgáltatás teljes élettartamát képviseli. A rendszer nem indít el további szolgáltatásokat, amíg az ExecuteAsync aszinkronná nem válik, például a hívással await. Kerülje a hosszú, blokkoló inicializáló munkát a ExecuteAsync-ban. A StopAsync(CancellationToken) esetén a gazda arra vár, hogy a ExecuteAsync befejeződjön.

A lemondási token az IHostedService.StopAsync hívásakor aktiválódik. Az Ön ExecuteAsync implementációjának azonnal be kell fejeződnie, amikor a megszakítási token aktiválódik, hogy a szolgáltatást megfelelően leállítsa. Ellenkező esetben a szolgáltatás kényszerítetten leáll a kikapcsolási időtúllépéskor. További információkért lásd az IHostedService felületének szakaszát.

További információ: BackgroundService forráskód.

Időzított háttérfeladatok

Az időzított háttérfeladatok a System.Threading.Timer osztályt használják . Az időzítő aktiválja a tevékenység metódusát DoWork . Az időzítő le van tiltva StopAsync, és ártalmatlanítva van, amikor a szolgáltatástároló ártalmatlanításra kerül Dispose.

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer = null;

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

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

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

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

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

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

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

        return Task.CompletedTask;
    }

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

A Timer rendszer nem várja meg, amíg a korábbi végrehajtások DoWork befejeződnek, ezért előfordulhat, hogy a bemutatott megközelítés nem minden forgatókönyvhöz megfelelő. Az Interlocked.Increment a végrehajtási számláló atomi műveletként történő növelésére szolgál, amely biztosítja, hogy több szál ne frissüljön executionCount egyidejűleg.

A szolgáltatás IHostBuilder.ConfigureServices regisztrálva van (Program.cs) a AddHostedService bővítménymetódussal.

services.AddHostedService<TimedHostedService>();

Háttérfeladatban egy hatókörű szolgáltatás használata

Ha hatókörrel rendelkező szolgáltatásokat szeretne használni a BackgroundService szolgáltatásban, hozzon létre egy hatókört. A rendszer alapértelmezés szerint nem hoz létre hatókört egy üzemeltetett szolgáltatáshoz.

A hatókörrel rendelkező háttérfeladat-szolgáltatás tartalmazza a háttértevékenység logikáját. Az alábbi példában:

  • A szolgáltatás aszinkron. A DoWork metódus egy Task. Bemutatás céljából a DoWork módszer esetében tíz másodperces késésre van szükség.
  • A szolgáltatásba injektálunk egy ILogger elemet.
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);
        }
    }
}

Az üzemeltetett szolgáltatás létrehoz egy hatókört, amely megoldja a hatókörrel rendelkező háttérfeladat-szolgáltatást annak DoWork metódusának meghívásához. DoWork visszaad egy Task-t, amelyet ExecuteAsync-ben várnak:

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

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

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

Várólistán lévő háttérfeladatok

A háttérfeladat-üzenetsor a .NET-keretrendszer 4.x-en QueueBackgroundWorkItemalapul:

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Az alábbi QueueHostedService példában:

  • A BackgroundProcessing metódus egy Task-t ad vissza, amit a ExecuteAsync-ben várunk.
  • Az üzenetsor háttérfeladatai le lesznek kérdezve és végrehajtva a következőben BackgroundProcessing: .
  • A munkaelemeket a szolgáltatás leállása előtt várjuk.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);
    }
}

A MonitorLoop szolgáltatás kezeli az üzemeltetett szolgáltatás lekérdezési feladatait, amikor a w kulcs ki van választva egy bemeneti eszközön:

  • A IBackgroundTaskQueue a MonitorLoop szolgáltatásba lett beszúrva.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem meghívásra kerül egy munkaelem sorba állításához.
  • A munkaelem egy hosszú ideig futó háttérfeladatot szimulál:
    • A rendszer három 5 másodperces késést hajt végre (Task.Delay).
    • Az try-catch utasítás elkapja a OperationCanceledException-t, ha a feladat lemondásra kerül.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue,
        ILogger<MonitorLoop> logger,
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("MonitorAsync Loop is starting.");

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

    private async ValueTask MonitorAsync()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

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

        while (!token.IsCancellationRequested && delayLoop < 3)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
            catch (OperationCanceledException)
            {
                // Prevent throwing if the Delay is cancelled
            }

            delayLoop++;

            _logger.LogInformation("Queued Background Task {Guid} is running. " 
                                   + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop a következőben Program.csindult el:

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

Aszinkron időzített háttérfeladat

A következő kód egy aszinkron időzített háttérfeladatot hoz létre:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        // When the timer should have no due-time, then do the work once now.
        await DoWork();

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

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

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

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

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

Natív AOT

A Feldolgozói szolgáltatás sablonjai támogatják a .NET natív előzetes fordítást (AOT) a --aot megadott jelölővel.

  1. Új projekt létrehozása.
  2. Válassza a Munkás szolgáltatáslehetőséget. Válassza a Következőlehetőséget.
  3. Adjon meg egy projektnevet a Projektnév mezőben, vagy fogadja el az alapértelmezett projektnevet. Válassza a Következőlehetőséget.
  4. A További információk párbeszédpanelen:
  5. Válasszon egy keretrendszert.
  6. Jelölje be a Natív AOT-közzététel engedélyezése jelölőnégyzetet.
  7. Válassza a Create gombot.

Az AOT-beállítás hozzáadja a <PublishAot>true</PublishAot> elemet a projektfájlhoz:


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

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
+   <PublishAot>true</PublishAot>
    <UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
  </ItemGroup>
</Project>

További erőforrások

A ASP.NET Core-ban a háttérfeladatok üzemeltetett szolgáltatásként implementálhatók. Az üzemeltetett szolgáltatás egy háttérfeladatlogikával rendelkező osztály, amely implementálja a IHostedService felületet. Ez a cikk három üzemeltetett szolgáltatás-példát tartalmaz:

Munkás szolgáltatás sablon

A ASP.NET Core Worker Service-sablon kiindulópontként szolgál a hosszú ideig futó szolgáltatásalkalmazások írásához. A Worker Service-sablonból létrehozott alkalmazás megadja a Worker SDK-t a projektfájlban:

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

Üzemeltetett szolgáltatási alkalmazás alapjaként sablont használni:

  1. Új projekt létrehozása.
  2. Válassza a Munkás szolgáltatáslehetőséget. Válassza a Következőlehetőséget.
  3. Adjon meg egy projektnevet a Projektnév mezőben, vagy fogadja el az alapértelmezett projektnevet. Válassza a Következőlehetőséget.
  4. A További információ párbeszédpanelen válasszon keretrendszert. Válassza a Create gombot.

Package

A Worker Service-sablonon alapuló alkalmazások az SDK-t Microsoft.NET.Sdk.Worker használják, és kifejezetten hivatkoznak a Microsoft.Extensions.Hosting csomagra. Lásd például a mintaalkalmazás projektfájlját (BackgroundTasksSample.csproj).

Az SDK-t használó Microsoft.NET.Sdk.Web webalkalmazások esetében a Microsoft.Extensions.Hosting csomagra implicit módon hivatkozik a megosztott keretrendszer. Az alkalmazás projektfájljában nincs szükség explicit csomaghivatkozásra.

IHostedService felület

Az IHostedService interfész két metódust határoz meg a gazda által kezelt objektumokhoz.

StartAsync

A StartAsync (CancellationToken) tartalmazza a háttérfeladat indítására vonatkozó logikát. StartAsync a kezdet előtt van meghívva:

StartAsync rövid ideig futó tevékenységekre kell korlátozódnia, mert az üzemeltetett szolgáltatások egymás után futnak, és a futtatások befejezéséig StartAsync nem indulnak el további szolgáltatások.

StopAsync

A megszakítási token alapértelmezett 30 másodperces időkorlátot alkalmaz annak jelzésére, hogy a leállítási folyamat már nem folytatódhat zökkenőmentesen. Amikor lemondást kérnek a tokenre:

  • Az alkalmazás által végrehajtott többi háttérműveletet le kell szakítani.
  • Minden behívott StopAsync metódusnak azonnal vissza kell térnie.

A lemondás kérése után azonban a feladatok nem lesznek félbehagyva – a hívó minden feladat befejezésére vár.

Ha az alkalmazás váratlanul leáll (például az alkalmazás folyamata meghiúsul), StopAsync lehet, hogy nem lesz meghívva. Ezért előfordulhat, hogy az StopAsync meghívott metódusok vagy végrehajtott műveletek nem valósulnak meg.

Az alapértelmezett 30 másodperces leállítási időtúllépés meghosszabbításához állítsa be a következőt:

Az üzemeltetett szolgáltatás az alkalmazás indításakor egyszer aktiválódik, és az alkalmazás leállításakor kecsesen leáll. Ha a háttérben futó feladat végrehajtása során hibaüzenet jelenik meg, akkor is meg kell hívni, Dispose ha StopAsync nincs meghívva.

BackgroundService alaposztály

BackgroundService egy alaposztály egy hosszú ideig futó IHostedServiceimplementáláshoz.

Az ExecuteAsync (CancellationToken) parancs a háttérszolgáltatás futtatására van meghívva. Az implementáció egy olyan értéket Task ad vissza, amely a háttérszolgáltatás teljes élettartamát képviseli. A rendszer nem indít el további szolgáltatásokat, amíg az ExecuteAsync aszinkronná nem válik, például a hívással await. Kerülje a hosszú, blokkoló inicializáló munkát a ExecuteAsync-ban. A StopAsync(CancellationToken) esetén a gazda arra vár, hogy a ExecuteAsync befejeződjön.

A lemondási token az IHostedService.StopAsync hívásakor aktiválódik. Az Ön ExecuteAsync implementációjának azonnal be kell fejeződnie, amikor a megszakítási token aktiválódik, hogy a szolgáltatást megfelelően leállítsa. Ellenkező esetben a szolgáltatás kényszerítetten leáll a kikapcsolási időtúllépéskor. További információkért lásd az IHostedService felületének szakaszát.

További információ: BackgroundService forráskód.

Időzított háttérfeladatok

Az időzított háttérfeladatok a System.Threading.Timer osztályt használják . Az időzítő aktiválja a tevékenység metódusát DoWork . Az időzítő le van tiltva StopAsync, és ártalmatlanítva van, amikor a szolgáltatástároló ártalmatlanításra kerül Dispose.

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer = null;

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

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

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

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

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

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

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

        return Task.CompletedTask;
    }

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

A Timer rendszer nem várja meg, amíg a korábbi végrehajtások DoWork befejeződnek, ezért előfordulhat, hogy a bemutatott megközelítés nem minden forgatókönyvhöz megfelelő. Az Interlocked.Increment a végrehajtási számláló atomi műveletként történő növelésére szolgál, amely biztosítja, hogy több szál ne frissüljön executionCount egyidejűleg.

A szolgáltatás IHostBuilder.ConfigureServices regisztrálva van (Program.cs) a AddHostedService bővítménymetódussal.

services.AddHostedService<TimedHostedService>();

Háttérfeladatban egy hatókörű szolgáltatás használata

Ha hatókörrel rendelkező szolgáltatásokat szeretne használni a BackgroundService szolgáltatásban, hozzon létre egy hatókört. A rendszer alapértelmezés szerint nem hoz létre hatókört egy üzemeltetett szolgáltatáshoz.

A hatókörrel rendelkező háttérfeladat-szolgáltatás tartalmazza a háttértevékenység logikáját. Az alábbi példában:

  • A szolgáltatás aszinkron. A DoWork metódus egy Task. Bemutatás céljából a DoWork módszer esetében tíz másodperces késésre van szükség.
  • A szolgáltatásba injektálunk egy ILogger elemet.
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);
        }
    }
}

Az üzemeltetett szolgáltatás létrehoz egy hatókört, amely megoldja a hatókörrel rendelkező háttérfeladat-szolgáltatást annak DoWork metódusának meghívásához. DoWork visszaad egy Task-t, amelyet ExecuteAsync-ben várnak:

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

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

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

Várólistán lévő háttérfeladatok

A háttérfeladat-üzenetsor a .NET-keretrendszer 4.x-en QueueBackgroundWorkItemalapul:

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Az alábbi QueueHostedService példában:

  • A BackgroundProcessing metódus egy Task-t ad vissza, amit a ExecuteAsync-ben várunk.
  • Az üzenetsor háttérfeladatai le lesznek kérdezve és végrehajtva a következőben BackgroundProcessing: .
  • A munkaelemeket a szolgáltatás leállása előtt várjuk.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);
    }
}

A MonitorLoop szolgáltatás kezeli az üzemeltetett szolgáltatás lekérdezési feladatait, amikor a w kulcs ki van választva egy bemeneti eszközön:

  • A IBackgroundTaskQueue a MonitorLoop szolgáltatásba lett beszúrva.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem meghívásra kerül egy munkaelem sorba állításához.
  • A munkaelem egy hosszú ideig futó háttérfeladatot szimulál:
    • A rendszer három 5 másodperces késést hajt végre (Task.Delay).
    • Az try-catch utasítás elkapja a OperationCanceledException-t, ha a feladat lemondásra kerül.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue,
        ILogger<MonitorLoop> logger,
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("MonitorAsync Loop is starting.");

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

    private async ValueTask MonitorAsync()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

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

        while (!token.IsCancellationRequested && delayLoop < 3)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
            catch (OperationCanceledException)
            {
                // Prevent throwing if the Delay is cancelled
            }

            delayLoop++;

            _logger.LogInformation("Queued Background Task {Guid} is running. " 
                                   + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop a következőben Program.csindult el:

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

Aszinkron időzített háttérfeladat

A következő kód egy aszinkron időzített háttérfeladatot hoz létre:

namespace TimedBackgroundTasks;

public class TimedHostedService : BackgroundService
{
    private readonly ILogger<TimedHostedService> _logger;
    private int _executionCount;

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        // When the timer should have no due-time, then do the work once now.
        await DoWork();

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

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

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

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

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

További erőforrások

A ASP.NET Core-ban a háttérfeladatok üzemeltetett szolgáltatásként implementálhatók. Az üzemeltetett szolgáltatás egy háttérfeladatlogikával rendelkező osztály, amely implementálja a IHostedService felületet. Ez a cikk három üzemeltetett szolgáltatás-példát tartalmaz:

Mintakód megtekintése vagy letöltése (hogyan töltsd le)

Munkás szolgáltatás sablon

A ASP.NET Core Worker Service-sablon kiindulópontként szolgál a hosszú ideig futó szolgáltatásalkalmazások írásához. A Worker Service-sablonból létrehozott alkalmazás megadja a Worker SDK-t a projektfájlban:

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

Üzemeltetett szolgáltatási alkalmazás alapjaként sablont használni:

  1. Új projekt létrehozása.
  2. Válassza a Munkás szolgáltatáslehetőséget. Válassza a Következőlehetőséget.
  3. Adjon meg egy projektnevet a Projektnév mezőben, vagy fogadja el az alapértelmezett projektnevet. Válassza a Create gombot.
  4. Az Új feldolgozói szolgáltatás létrehozása párbeszédpanelen válassza a létrehozása lehetőséget.

Package

A Worker Service-sablonon alapuló alkalmazások az SDK-t Microsoft.NET.Sdk.Worker használják, és kifejezetten hivatkoznak a Microsoft.Extensions.Hosting csomagra. Lásd például a mintaalkalmazás projektfájlját (BackgroundTasksSample.csproj).

Az SDK-t használó Microsoft.NET.Sdk.Web webalkalmazások esetében a Microsoft.Extensions.Hosting csomagra implicit módon hivatkozik a megosztott keretrendszer. Az alkalmazás projektfájljában nincs szükség explicit csomaghivatkozásra.

IHostedService felület

Az IHostedService interfész két metódust határoz meg a gazda által kezelt objektumokhoz.

StartAsync

StartAsync A háttérfeladat indításához használni kívánt logikát tartalmazza. StartAsync a kezdet előtt van meghívva:

Az alapértelmezett viselkedés módosítható úgy, hogy az üzemeltetett szolgáltatás StartAsync az alkalmazás folyamatának konfigurálása és ApplicationStarted meghívása után fusson. Az alapértelmezett viselkedés módosításához, adja hozzá a hívás VideosWatcher után az üzemeltetett szolgáltatást (ConfigureWebHostDefaults az alábbi példában).

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

A törlési token alapértelmezett öt másodperces időtúllépéssel rendelkezik, amely azt jelzi, hogy a leállítási folyamat ne legyen többé zökkenőmentes. Amikor lemondást kérnek a tokenre:

  • Az alkalmazás által végrehajtott többi háttérműveletet le kell szakítani.
  • Minden behívott StopAsync metódusnak azonnal vissza kell térnie.

A lemondás kérése után azonban a feladatok nem lesznek félbehagyva – a hívó minden feladat befejezésére vár.

Ha az alkalmazás váratlanul leáll (például az alkalmazás folyamata meghiúsul), StopAsync lehet, hogy nem lesz meghívva. Ezért előfordulhat, hogy az StopAsync meghívott metódusok vagy végrehajtott műveletek nem valósulnak meg.

Az alapértelmezett öt másodperces leállítási időtúllépés meghosszabbításához állítsa be a következőt:

Az üzemeltetett szolgáltatás az alkalmazás indításakor egyszer aktiválódik, és az alkalmazás leállításakor kecsesen leáll. Ha a háttérben futó feladat végrehajtása során hibaüzenet jelenik meg, akkor is meg kell hívni, Dispose ha StopAsync nincs meghívva.

BackgroundService alaposztály

BackgroundService egy alaposztály egy hosszú ideig futó IHostedServiceimplementáláshoz.

Az ExecuteAsync (CancellationToken) parancs a háttérszolgáltatás futtatására van meghívva. Az implementáció egy olyan értéket Task ad vissza, amely a háttérszolgáltatás teljes élettartamát képviseli. A rendszer nem indít el további szolgáltatásokat, amíg az ExecuteAsync aszinkronná nem válik, például a hívással await. Kerülje a hosszú, blokkoló inicializáló munkát a ExecuteAsync-ban. A StopAsync(CancellationToken) esetén a gazda arra vár, hogy a ExecuteAsync befejeződjön.

A lemondási token az IHostedService.StopAsync hívásakor aktiválódik. Az Ön ExecuteAsync implementációjának azonnal be kell fejeződnie, amikor a megszakítási token aktiválódik, hogy a szolgáltatást megfelelően leállítsa. Ellenkező esetben a szolgáltatás kényszerítetten leáll a kikapcsolási időtúllépéskor. További információkért lásd az IHostedService felületének szakaszát.

StartAsync rövid ideig futó tevékenységekre kell korlátozódnia, mert az üzemeltetett szolgáltatások egymás után futnak, és a futtatások befejezéséig StartAsync nem indulnak el további szolgáltatások. A hosszú ideig futó feladatokat be kell helyezni a fájlba ExecuteAsync. További információt a BackgroundService forrásában talál.

Időzított háttérfeladatok

Az időzított háttérfeladatok a System.Threading.Timer osztályt használják . Az időzítő aktiválja a tevékenység metódusát DoWork . Az időzítő le van tiltva StopAsync, és ártalmatlanítva van, amikor a szolgáltatástároló ártalmatlanításra kerül 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();
    }
}

A Timer rendszer nem várja meg, amíg a korábbi végrehajtások DoWork befejeződnek, ezért előfordulhat, hogy a bemutatott megközelítés nem minden forgatókönyvhöz megfelelő. Az Interlocked.Increment a végrehajtási számláló atomi műveletként történő növelésére szolgál, amely biztosítja, hogy több szál ne frissüljön executionCount egyidejűleg.

A szolgáltatás IHostBuilder.ConfigureServices regisztrálva van (Program.cs) a AddHostedService bővítménymetódussal.

services.AddHostedService<TimedHostedService>();

Háttérfeladatban egy hatókörű szolgáltatás használata

Ha hatókörrel rendelkező szolgáltatásokat szeretne használni a BackgroundService szolgáltatásban, hozzon létre egy hatókört. A rendszer alapértelmezés szerint nem hoz létre hatókört egy üzemeltetett szolgáltatáshoz.

A hatókörrel rendelkező háttérfeladat-szolgáltatás tartalmazza a háttértevékenység logikáját. Az alábbi példában:

  • A szolgáltatás aszinkron. A DoWork metódus egy Task. Bemutatás céljából a DoWork módszer esetében tíz másodperces késésre van szükség.
  • A szolgáltatásba injektálunk egy ILogger elemet.
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);
        }
    }
}

Az üzemeltetett szolgáltatás létrehoz egy hatókört, amely megoldja a hatókörrel rendelkező háttérfeladat-szolgáltatást annak DoWork metódusának meghívásához. DoWork visszaad egy Task-t, amelyet ExecuteAsync-ben várnak:

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

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

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

Várólistán lévő háttérfeladatok

A háttérfeladat-üzenetsor a .NET-keretrendszer 4.x-en QueueBackgroundWorkItemalapul:

public interface IBackgroundTaskQueue
{
    ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        // Capacity should be set based on the expected application load and
        // number of concurrent threads accessing the queue.            
        // BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
        // which completes only when space became available. This leads to backpressure,
        // in case too many publishers/calls start accumulating.
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
    }

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        var workItem = await _queue.Reader.ReadAsync(cancellationToken);

        return workItem;
    }
}

Az alábbi QueueHostedService példában:

  • A BackgroundProcessing metódus egy Task-t ad vissza, amit a ExecuteAsync-ben várunk.
  • Az üzenetsor háttérfeladatai le lesznek kérdezve és végrehajtva a következőben BackgroundProcessing: .
  • A munkaelemeket a szolgáltatás leállása előtt várjuk.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);
    }
}

A MonitorLoop szolgáltatás kezeli az üzemeltetett szolgáltatás lekérdezési feladatait, amikor a w kulcs ki van választva egy bemeneti eszközön:

  • A IBackgroundTaskQueue a MonitorLoop szolgáltatásba lett beszúrva.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem meghívásra kerül egy munkaelem sorba állításához.
  • A munkaelem egy hosszú ideig futó háttérfeladatot szimulál:
    • A rendszer három 5 másodperces késést hajt végre (Task.Delay).
    • Az try-catch utasítás elkapja a OperationCanceledException-t, ha a feladat lemondásra kerül.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("MonitorAsync Loop is starting.");

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

    private async ValueTask MonitorAsync()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
            }
        }
    }

    private async ValueTask BuildWorkItem(CancellationToken token)
    {
        // Simulate three 5-second tasks to complete
        // for each enqueued work item

        int delayLoop = 0;
        var guid = Guid.NewGuid().ToString();

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

        while (!token.IsCancellationRequested && delayLoop < 3)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
            catch (OperationCanceledException)
            {
                // Prevent throwing if the Delay is cancelled
            }

            delayLoop++;

            _logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
        }

        if (delayLoop == 3)
        {
            _logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
        }
        else
        {
            _logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
        }
    }
}

A szolgáltatások regisztrálva vannak IHostBuilder.ConfigureServices (Program.cs). A üzemeltetett szolgáltatás regisztrálva van a AddHostedService bővítménymetódussal:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
    if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
        queueCapacity = 100;
    return new BackgroundTaskQueue(queueCapacity);
});

MonitorLoop a következőben Program.Mainindult el:

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

További erőforrások