Úlohy na pozadí s hostovanými službami v ASP.NET Core
Autor: Jeow Li Huan
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete v tomto článku ve verzi .NET 9.
V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:
- Úloha na pozadí, která běží v časovači.
- Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
- Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.
Šablona služby pracovního procesu
Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Použití šablony jako základu pro aplikaci hostovaných služeb:
- Vytvoření nového projektu
- Vyberte Službu pracovního procesu. Vyberte Další.
- Do pole Název projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Další.
- V dialogovém okně Další informace zvolte architekturu. Vyberte Vytvořit.
Balíček
Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker
sadu SDK a obsahuje explicitní odkaz na balíček Microsoft.Extensions.Hosting . Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample.csproj
).
U webových aplikací, které používají Microsoft.NET.Sdk.Web
sadu SDK, se na balíček Microsoft.Extensions.Hosting implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.
Rozhraní IHostedService
Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:
StartAsync
StartAsync(CancellationToken) obsahuje logiku pro spuštění úlohy na pozadí. StartAsync
je volána před:
- Konfiguruje se kanál zpracování požadavků aplikace.
- Server se spustí a aktivuje se IApplicationLifetime.ApplicationStarted .
StartAsync
měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync
se nespustí do dokončení.
StopAsync
- StopAsync(CancellationToken) se aktivuje, když hostitel provádí řádné vypnutí.
StopAsync
obsahuje logiku pro ukončení úlohy na pozadí. Implementujte IDisposable a finalizační metody (destruktory) pro odstranění jakýchkoli nespravovaných prostředků.
Token zrušení má výchozí 30sekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:
- Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
- Všechny metody volané volané by
StopAsync
se měly okamžitě vrátit.
Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.
Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync
nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync
nebo provedli operace.
Pokud chcete prodloužit výchozí časový limit 30sekundového vypnutí, nastavte:
- ShutdownTimeout při použití obecného hostitele. Další informace najdete v tématu Obecný hostitel .NET v ASP.NET Core.
- Nastavení konfigurace hostitele časového limitu při použití webového hostitele Další informace najdete v tématu ASP.NET Core Web Host.
Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose
v případě, že StopAsync
není volána.
Základní třída BackgroundService
BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.
Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await
. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync
. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync
dokončení.
Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync
by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.
Další informace najdete ve zdrojovém kódu BackgroundService .
Úkoly na pozadí s časovým limitem
Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork
úlohy. Časovač je zakázán StopAsync
a uvolněn, když je kontejner služby uvolněn: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();
}
}
Nečeká Timer na dokončení předchozích spuštění DoWork
, takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount
souběžně.
Služba je zaregistrovaná v IHostBuilder.ConfigureServices
(Program.cs
) pomocí AddHostedService
metody rozšíření:
services.AddHostedService<TimedHostedService>();
Využívání služby s vymezeným oborem v úloze na pozadí
Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.
Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:
- Služba je asynchronní. Metoda
DoWork
vrátíTask
hodnotu . Pro demonstrační účely je vDoWork
metodě očekáváno zpoždění deseti sekund. - Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork
metodu. DoWork
vrátí hodnotu Task
, která je očekávána v 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
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Úlohy na pozadí ve frontě
Fronta úloh na pozadí je založená na rozhraní .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
V následujícím QueueHostedService
příkladu:
- Metoda
BackgroundProcessing
vrátíTask
hodnotu , která je očekávána vExecuteAsync
. - Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v
BackgroundProcessing
. - Pracovní položky jsou očekávána před zastavením
StopAsync
služby .
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);
}
}
MonitorLoop
Služba zpracovává úlohy zařazení do fronty hostované služby při každém w
výběru klíče na vstupním zařízení:
- Vloží se
IBackgroundTaskQueue
doMonitorLoop
služby. IBackgroundTaskQueue.QueueBackgroundWorkItem
je volána k vytvoření fronty pracovní položky.- Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
- Spustí se tři 5sekundová zpoždění (
Task.Delay
). - Příkaz
try-catch
zachytí OperationCanceledException , pokud je úkol zrušen.
- Spustí se tři 5sekundová zpoždění (
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);
}
}
}
Služby jsou zaregistrované v IHostBuilder.ConfigureServices
(Program.cs
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
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
je spuštěn v Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchronní časovaná úloha na pozadí
Následující kód vytvoří asynchronní časově uspořádanou úlohu na pozadí:
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.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Nativní AOT
Šablony služby Pracovních procesů podporují nativní .NET předem (AOT) s příznakem --aot
:
- Vytvoření nového projektu
- Vyberte Službu pracovního procesu. Vyberte Další.
- Do pole Název projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Další.
- V dialogovém okně Další informace :
- Zvolte architekturu.
- Zaškrtněte políčko Povolit nativní publikování AOT.
- Vyberte Vytvořit.
Možnost AOT přidá <PublishAot>true</PublishAot>
do souboru projektu:
<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>
Další materiály
V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:
- Úloha na pozadí, která běží v časovači.
- Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
- Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.
Šablona služby pracovního procesu
Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Použití šablony jako základu pro aplikaci hostovaných služeb:
- Vytvoření nového projektu
- Vyberte Službu pracovního procesu. Vyberte Další.
- Do pole Název projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Další.
- V dialogovém okně Další informace zvolte architekturu. Vyberte Vytvořit.
Balíček
Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker
sadu SDK a obsahuje explicitní odkaz na balíček Microsoft.Extensions.Hosting . Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample.csproj
).
U webových aplikací, které používají Microsoft.NET.Sdk.Web
sadu SDK, se na balíček Microsoft.Extensions.Hosting implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.
Rozhraní IHostedService
Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:
StartAsync
StartAsync(CancellationToken) obsahuje logiku pro spuštění úlohy na pozadí. StartAsync
je volána před:
- Konfiguruje se kanál zpracování požadavků aplikace.
- Server se spustí a aktivuje se IApplicationLifetime.ApplicationStarted .
StartAsync
měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync
se nespustí do dokončení.
StopAsync
- StopAsync(CancellationToken) se aktivuje, když hostitel provádí řádné vypnutí.
StopAsync
obsahuje logiku pro ukončení úlohy na pozadí. Implementujte IDisposable a finalizační metody (destruktory) pro odstranění jakýchkoli nespravovaných prostředků.
Token zrušení má výchozí 30sekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:
- Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
- Všechny metody volané volané by
StopAsync
se měly okamžitě vrátit.
Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.
Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync
nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync
nebo provedli operace.
Pokud chcete prodloužit výchozí časový limit 30sekundového vypnutí, nastavte:
- ShutdownTimeout při použití obecného hostitele. Další informace najdete v tématu Obecný hostitel .NET v ASP.NET Core.
- Nastavení konfigurace hostitele časového limitu při použití webového hostitele Další informace najdete v tématu ASP.NET Core Web Host.
Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose
v případě, že StopAsync
není volána.
Základní třída BackgroundService
BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.
Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await
. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync
. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync
dokončení.
Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync
by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.
Další informace najdete ve zdrojovém kódu BackgroundService .
Úkoly na pozadí s časovým limitem
Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork
úlohy. Časovač je zakázán StopAsync
a uvolněn, když je kontejner služby uvolněn: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();
}
}
Nečeká Timer na dokončení předchozích spuštění DoWork
, takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount
souběžně.
Služba je zaregistrovaná v IHostBuilder.ConfigureServices
(Program.cs
) pomocí AddHostedService
metody rozšíření:
services.AddHostedService<TimedHostedService>();
Využívání služby s vymezeným oborem v úloze na pozadí
Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.
Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:
- Služba je asynchronní. Metoda
DoWork
vrátíTask
hodnotu . Pro demonstrační účely je vDoWork
metodě očekáváno zpoždění deseti sekund. - Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork
metodu. DoWork
vrátí hodnotu Task
, která je očekávána v 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
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Úlohy na pozadí ve frontě
Fronta úloh na pozadí je založená na rozhraní .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
V následujícím QueueHostedService
příkladu:
- Metoda
BackgroundProcessing
vrátíTask
hodnotu , která je očekávána vExecuteAsync
. - Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v
BackgroundProcessing
. - Pracovní položky jsou očekávána před zastavením
StopAsync
služby .
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);
}
}
MonitorLoop
Služba zpracovává úlohy zařazení do fronty hostované služby při každém w
výběru klíče na vstupním zařízení:
- Vloží se
IBackgroundTaskQueue
doMonitorLoop
služby. IBackgroundTaskQueue.QueueBackgroundWorkItem
je volána k vytvoření fronty pracovní položky.- Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
- Spustí se tři 5sekundová zpoždění (
Task.Delay
). - Příkaz
try-catch
zachytí OperationCanceledException , pokud je úkol zrušen.
- Spustí se tři 5sekundová zpoždění (
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);
}
}
}
Služby jsou zaregistrované v IHostBuilder.ConfigureServices
(Program.cs
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
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
je spuštěn v Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchronní časovaná úloha na pozadí
Následující kód vytvoří asynchronní časově uspořádanou úlohu na pozadí:
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.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Další materiály
V ASP.NET Core je možné úlohy na pozadí implementovat jako hostované služby. Hostovaná služba je třída s logikou úloh na pozadí, která implementuje IHostedService rozhraní. Tento článek obsahuje tři příklady hostované služby:
- Úloha na pozadí, která běží v časovači.
- Hostovaná služba, která aktivuje vymezenou službu. Vymezená služba může používat injektáž závislostí (DI).
- Úlohy na pozadí zařazené do fronty, které se spouštějí postupně.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Šablona služby pracovního procesu
Šablona služby ASP.NET Core Worker Service představuje výchozí bod pro psaní dlouhotrvajících aplikací služby. Aplikace vytvořená ze šablony Služby pracovního procesu určuje sadu SDK pracovního procesu v souboru projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Použití šablony jako základu pro aplikaci hostovaných služeb:
- Vytvoření nového projektu
- Vyberte Službu pracovního procesu. Vyberte Další.
- Do pole Název projektu zadejte název projektu nebo přijměte výchozí název projektu. Vyberte Vytvořit.
- V dialogovém okně Vytvořit novou službu pracovního procesu vyberte Vytvořit.
Balíček
Aplikace založená na šabloně pracovní služby používá Microsoft.NET.Sdk.Worker
sadu SDK a obsahuje explicitní odkaz na balíček Microsoft.Extensions.Hosting . Podívejte se například na soubor projektu ukázkové aplikace (BackgroundTasksSample.csproj
).
U webových aplikací, které používají Microsoft.NET.Sdk.Web
sadu SDK, se na balíček Microsoft.Extensions.Hosting implicitně odkazuje ze sdílené architektury. Explicitní odkaz na balíček v souboru projektu aplikace není povinný.
Rozhraní IHostedService
Rozhraní IHostedService definuje dvě metody pro objekty spravované hostitelem:
StartAsync
StartAsync
obsahuje logiku pro spuštění úlohy na pozadí. StartAsync
je volána před:
- Konfiguruje se kanál zpracování požadavků aplikace.
- Server se spustí a aktivuje se IApplicationLifetime.ApplicationStarted .
Výchozí chování je možné změnit tak, aby se hostovaná služba StartAsync
spustila po nakonfigurování kanálu aplikace a ApplicationStarted
zavolána. Pokud chcete změnit výchozí chování, přidejte hostované služby (VideosWatcher
v následujícím příkladu) po volání 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
- StopAsync(CancellationToken) se aktivuje, když hostitel provádí řádné vypnutí.
StopAsync
obsahuje logiku pro ukončení úlohy na pozadí. Implementujte IDisposable a finalizační metody (destruktory) pro odstranění jakýchkoli nespravovaných prostředků.
Token zrušení má výchozí pětisekundový časový limit, který značí, že proces vypnutí by už neměl být bez odkladu. Při vyžádání zrušení tokenu:
- Všechny zbývající operace na pozadí, které aplikace provádí, by se měly přerušit.
- Všechny metody volané volané by
StopAsync
se měly okamžitě vrátit.
Po vyžádání zrušení se ale úkoly neopustí – volající čeká na dokončení všech úkolů.
Pokud se aplikace neočekávaně vypne (například proces aplikace selže), StopAsync
nemusí se volat. Proto nemusí dojít k žádným metodám, které volali StopAsync
nebo provedli operace.
Pokud chcete prodloužit výchozí časový limit pětisekundového vypnutí, nastavte:
- ShutdownTimeout při použití obecného hostitele. Další informace najdete v tématu Obecný hostitel .NET v ASP.NET Core.
- Nastavení konfigurace hostitele časového limitu při použití webového hostitele Další informace najdete v tématu ASP.NET Core Web Host.
Hostovaná služba se aktivuje jednou při spuštění aplikace a řádně se vypne při vypnutí aplikace. Pokud při provádění úlohy na pozadí dojde k chybě, měla by se volat i Dispose
v případě, že StopAsync
není volána.
Základní třída BackgroundService
BackgroundService je základní třída pro implementaci dlouhotrvající IHostedService.
Ke spuštění služby na pozadí se volá executeAsync(CancellationToken ). Implementace vrátí Task hodnotu, která představuje celou životnost služby na pozadí. Žádné další služby se nespustí, dokud se ExecuteAsync nestane asynchronním, například voláním await
. Vyhněte se dlouhému provádění, blokování inicializace práce v ExecuteAsync
. Bloky hostitelů v stopAsync(CancellationToken) čekají na ExecuteAsync
dokončení.
Token zrušení se aktivuje při volání IHostedService.StopAsync . Vaše implementace ExecuteAsync
by se měla dokončit okamžitě, když se aktivuje token zrušení, aby se služba řádně vypnula. V opačném případě se služba v vypršení časového limitu vypnutí vypne. Další informace najdete v části rozhraní IHostedService.
StartAsync
měly by být omezené na krátké úlohy, protože hostované služby se spouštějí postupně a dokud se nespustí další služby, dokud StartAsync
se nespustí do dokončení. Dlouhotrvající úkoly by měly být umístěny do ExecuteAsync
souboru . Další informace najdete ve zdroji služby BackgroundService.
Úkoly na pozadí s časovým limitem
Časovaná úloha na pozadí využívá System.Threading.Timer třídy. Časovač aktivuje metodu DoWork
úlohy. Časovač je zakázán StopAsync
a uvolněn, když je kontejner služby uvolněn: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á Timer na dokončení předchozích spuštění DoWork
, takže zobrazený přístup nemusí být vhodný pro každý scénář. Interlocked.Increment se používá k zvýšení čítače provádění jako atomické operace, která zajišťuje, že více vláken se neaktualizuje executionCount
souběžně.
Služba je zaregistrovaná v IHostBuilder.ConfigureServices
(Program.cs
) pomocí AddHostedService
metody rozšíření:
services.AddHostedService<TimedHostedService>();
Využívání služby s vymezeným oborem v úloze na pozadí
Pokud chcete použít vymezené služby v rámci služby BackgroundService, vytvořte obor. Pro hostované služby se ve výchozím nastavení nevytvořil žádný obor.
Služba úloh na pozadí s vymezeným oborem obsahuje logiku úlohy na pozadí. V následujícím příkladu:
- Služba je asynchronní. Metoda
DoWork
vrátíTask
hodnotu . Pro demonstrační účely je vDoWork
metodě očekáváno zpoždění deseti sekund. - Do služby se vloží an ILogger .
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, který přeloží službu úlohy na pozadí s vymezeným oborem a zavolá její DoWork
metodu. DoWork
vrátí hodnotu Task
, která je očekávána v 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
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Úlohy na pozadí ve frontě
Fronta úloh na pozadí je založená na rozhraní .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
V následujícím QueueHostedService
příkladu:
- Metoda
BackgroundProcessing
vrátíTask
hodnotu , která je očekávána vExecuteAsync
. - Úlohy na pozadí ve frontě jsou vyřazeny z fronty a spuštěny v
BackgroundProcessing
. - Pracovní položky jsou očekávána před zastavením
StopAsync
služby .
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);
}
}
MonitorLoop
Služba zpracovává úlohy zařazení do fronty hostované služby při každém w
výběru klíče na vstupním zařízení:
- Vloží se
IBackgroundTaskQueue
doMonitorLoop
služby. IBackgroundTaskQueue.QueueBackgroundWorkItem
je volána k vytvoření fronty pracovní položky.- Pracovní položka simuluje dlouhotrvající úlohu na pozadí:
- Spustí se tři 5sekundová zpoždění (
Task.Delay
). - Příkaz
try-catch
zachytí OperationCanceledException , pokud je úkol zrušen.
- Spustí se tři 5sekundová zpoždění (
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);
}
}
}
Služby jsou zaregistrované v IHostBuilder.ConfigureServices
(Program.cs
). Hostovaná služba je zaregistrovaná pomocí AddHostedService
metody rozšíření:
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
je spuštěn v Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();