Zadania w tle z hostowanymi usługami w ASP.NET Core
Przez Jeow Li Huan
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
W ASP.NET Core zadania w tle można zaimplementować jako usługi hostowane. Hostowana usługa to klasa z logiką zadań w tle, która implementuje IHostedService interfejs. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane na czasomierzu.
- Hostowana usługa, która aktywuje usługę o określonym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle w kolejce, które są uruchamiane sekwencyjnie.
Szablon usługi procesu roboczego
Szablon usługi ASP.NET Core Worker Service stanowi punkt wyjścia do pisania długotrwałych aplikacji usługi. Aplikacja utworzona na podstawie szablonu usługi Worker Service określa zestaw SDK procesu roboczego w pliku projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa procesu roboczego. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym Dodatkowe informacje wybierz strukturę. Wybierz pozycję Utwórz.
Pakiet
Aplikacja oparta na szablonie usługi roboczej używa Microsoft.NET.Sdk.Worker
zestawu SDK i zawiera jawne odwołanie do pakietu Microsoft.Extensions.Hosting . Zobacz na przykład plik projektu przykładowej aplikacji (BackgroundTasksSample.csproj
).
W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web
zestawu SDK pakiet Microsoft.Extensions.Hosting odwołuje się niejawnie z udostępnionej platformy. Jawne odwołanie do pakietu w pliku projektu aplikacji nie jest wymagane.
Interfejs IHostedService
Interfejs IHostedService definiuje dwie metody dla obiektów zarządzanych przez hosta:
StartAsync
StartAsync(CancellationToken) zawiera logikę uruchamiania zadania w tle. StartAsync
jest wywoływana przed:
- Potok przetwarzania żądań aplikacji jest skonfigurowany.
- Serwer jest uruchomiony i wyzwalany jest element IApplicationLifetime.ApplicationStarted .
StartAsync
powinno być ograniczone do krótkich uruchomionych zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane do momentu StartAsync
ukończenia.
StopAsync
- Funkcja StopAsync(CancellationToken) jest wyzwalana, gdy host wykonuje bezproblemowe zamykanie.
StopAsync
zawiera logikę, która ma zakończyć zadanie w tle. Zaimplementuj IDisposable i finalizatory (destruktory) do usuwania wszystkich niezarządzanych zasobów.
Token anulowania ma domyślny limit czasu 30 sekund, aby wskazać, że proces zamykania nie powinien już być wdziękny. Po zażądaniu anulowania w tokenie:
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsync
metodach powinny zostać zwrócone natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na ukończenie wszystkich zadań.
Jeśli aplikacja zostanie nieoczekiwanie zamknięta (na przykład proces aplikacji zakończy się niepowodzeniem), StopAsync
może nie zostać wywołana. W związku z tym wszelkie metody wywoływane lub wykonywane w programie StopAsync
operacje mogą nie wystąpić.
Aby przedłużyć domyślny limit czasu zamknięcia 30 sekund, ustaw:
- ShutdownTimeout w przypadku korzystania z hosta ogólnego. Aby uzyskać więcej informacji, zobacz Host ogólny platformy ASP.NET Core.
- Ustawienie konfiguracji hosta limitu czasu zamknięcia podczas korzystania z hosta sieci Web. Aby uzyskać więcej informacji, zobacz ASP.NET Core Web Host.
Hostowana usługa jest aktywowana raz podczas uruchamiania aplikacji i bezpiecznie zamykana po zamknięciu aplikacji. Jeśli podczas wykonywania zadania w tle zostanie zgłoszony błąd, powinien zostać wywołany nawet wtedy, Dispose
gdy StopAsync
nie zostanie wywołany.
BackgroundService, klasa bazowa
BackgroundService jest klasą bazową do implementowania długotrwałego IHostedServiceelementu .
Funkcja ExecuteAsync(CancellationToken) jest wywoływana w celu uruchomienia usługi w tle. Implementacja zwraca wartość Task reprezentującą cały okres istnienia usługi w tle. Żadne dalsze usługi nie są uruchamiane, dopóki funkcja ExecuteAsync nie stanie się asynchroniczna, na przykład przez wywołanie metody await
. Unikaj wykonywania długich, blokujących pracę inicjowania w programie ExecuteAsync
. Bloki hosta w stopAsync(CancellationToken) oczekujące na ExecuteAsync
zakończenie.
Token anulowania jest wyzwalany po wywołaniu wywołania IHostedService.StopAsync . Implementacja polecenia ExecuteAsync
powinna zakończyć się natychmiast, gdy token anulowania zostanie wyzwolony, aby bezpiecznie zamknąć usługę. W przeciwnym razie usługa niegracyjnie zamyka się po przekroczeniu limitu czasu zamknięcia. Aby uzyskać więcej informacji, zobacz sekcję interfejsu IHostedService.
Aby uzyskać więcej informacji, zobacz kod źródłowy usługi BackgroundService .
Czasowe zadania w tle
Zadanie w tle czasowe korzysta z klasy System.Threading.Timer . Czasomierz wyzwala metodę DoWork
zadania. Czasomierz jest wyłączony StopAsync
i usuwany po usunięciu kontenera usługi w systemie 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();
}
}
Element Timer nie czeka na ukończenie poprzednich DoWork
wykonań, więc pokazane podejście może nie być odpowiednie dla każdego scenariusza. Interlocked.Increment służy do przyrostowania licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie jest aktualizowanych executionCount
jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices
(Program.cs
) przy użyciu AddHostedService
metody rozszerzenia:
services.AddHostedService<TimedHostedService>();
Korzystanie z usługi o określonym zakresie w zadaniu w tle
Aby używać usług o określonym zakresie w ramach usługi BackgroundService, utwórz zakres. Domyślnie dla hostowanej usługi nie jest tworzony żaden zakres.
Zakres usługi zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWork
zwraca wartośćTask
. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork
. - Element ILogger jest wstrzykiwany do usługi.
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);
}
}
}
Usługa hostowana tworzy zakres, aby rozpoznać zakres usługi zadań w tle w celu wywołania jej DoWork
metody. DoWork
zwraca element , który jest oczekiwany w elemecie Task
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);
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle w kolejce
Kolejka zadań w tle jest oparta na platformie .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;
}
}
W poniższym QueueHostedService
przykładzie:
- Metoda
BackgroundProcessing
zwracaTask
element , który jest oczekiwany w plikuExecuteAsync
. - Zadania w tle w kolejce są usuwane z kolejki i wykonywane w pliku
BackgroundProcessing
. - Elementy robocze są oczekiwane przed zatrzymanie usługi w programie
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);
}
}
MonitorLoop
Usługa obsługuje kolejkowanie zadań dla hostowanej usługi za każdym razem, gdy w
klucz jest wybrany na urządzeniu wejściowym:
- Element
IBackgroundTaskQueue
jest wstrzykiwany doMonitorLoop
usługi. IBackgroundTaskQueue.QueueBackgroundWorkItem
element jest wywoływany w celu kolejkowania elementu roboczego.- Element roboczy symuluje długotrwałe zadanie w tle:
- Wykonywane są trzy 5-sekundowe opóźnienia (
Task.Delay
). - Instrukcja
try-catch
wychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Wykonywane są trzy 5-sekundowe opóźnienia (
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);
}
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
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
program jest uruchamiany w pliku Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchroniczne zadanie w tle z czasem
Poniższy kod tworzy asynchroniczne zadanie w tle:
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);
}
}
Natywna AOT
Szablony usługi Roboczej obsługują natywną platformę .NET z wyprzedzeniem (AOT) z flagą --aot
:
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa procesu roboczego. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym Dodatkowe informacje:
- Wybierz platformę.
- Zaznacz pole wyboru Włącz natywną publikację AOT.
- Wybierz pozycję Utwórz.
Opcja AOT dodaje <PublishAot>true</PublishAot>
do pliku 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>
Dodatkowe zasoby
W ASP.NET Core zadania w tle można zaimplementować jako usługi hostowane. Hostowana usługa to klasa z logiką zadań w tle, która implementuje IHostedService interfejs. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane na czasomierzu.
- Hostowana usługa, która aktywuje usługę o określonym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle w kolejce, które są uruchamiane sekwencyjnie.
Szablon usługi procesu roboczego
Szablon usługi ASP.NET Core Worker Service stanowi punkt wyjścia do pisania długotrwałych aplikacji usługi. Aplikacja utworzona na podstawie szablonu usługi Worker Service określa zestaw SDK procesu roboczego w pliku projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa procesu roboczego. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym Dodatkowe informacje wybierz strukturę. Wybierz pozycję Utwórz.
Pakiet
Aplikacja oparta na szablonie usługi roboczej używa Microsoft.NET.Sdk.Worker
zestawu SDK i zawiera jawne odwołanie do pakietu Microsoft.Extensions.Hosting . Zobacz na przykład plik projektu przykładowej aplikacji (BackgroundTasksSample.csproj
).
W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web
zestawu SDK pakiet Microsoft.Extensions.Hosting odwołuje się niejawnie z udostępnionej platformy. Jawne odwołanie do pakietu w pliku projektu aplikacji nie jest wymagane.
Interfejs IHostedService
Interfejs IHostedService definiuje dwie metody dla obiektów zarządzanych przez hosta:
StartAsync
StartAsync(CancellationToken) zawiera logikę uruchamiania zadania w tle. StartAsync
jest wywoływana przed:
- Potok przetwarzania żądań aplikacji jest skonfigurowany.
- Serwer jest uruchomiony i wyzwalany jest element IApplicationLifetime.ApplicationStarted .
StartAsync
powinno być ograniczone do krótkich uruchomionych zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane do momentu StartAsync
ukończenia.
StopAsync
- Funkcja StopAsync(CancellationToken) jest wyzwalana, gdy host wykonuje bezproblemowe zamykanie.
StopAsync
zawiera logikę, która ma zakończyć zadanie w tle. Zaimplementuj IDisposable i finalizatory (destruktory) do usuwania wszystkich niezarządzanych zasobów.
Token anulowania ma domyślny limit czasu 30 sekund, aby wskazać, że proces zamykania nie powinien już być wdziękny. Po zażądaniu anulowania w tokenie:
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsync
metodach powinny zostać zwrócone natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na ukończenie wszystkich zadań.
Jeśli aplikacja zostanie nieoczekiwanie zamknięta (na przykład proces aplikacji zakończy się niepowodzeniem), StopAsync
może nie zostać wywołana. W związku z tym wszelkie metody wywoływane lub wykonywane w programie StopAsync
operacje mogą nie wystąpić.
Aby przedłużyć domyślny limit czasu zamknięcia 30 sekund, ustaw:
- ShutdownTimeout w przypadku korzystania z hosta ogólnego. Aby uzyskać więcej informacji, zobacz Host ogólny platformy ASP.NET Core.
- Ustawienie konfiguracji hosta limitu czasu zamknięcia podczas korzystania z hosta sieci Web. Aby uzyskać więcej informacji, zobacz ASP.NET Core Web Host.
Hostowana usługa jest aktywowana raz podczas uruchamiania aplikacji i bezpiecznie zamykana po zamknięciu aplikacji. Jeśli podczas wykonywania zadania w tle zostanie zgłoszony błąd, powinien zostać wywołany nawet wtedy, Dispose
gdy StopAsync
nie zostanie wywołany.
BackgroundService, klasa bazowa
BackgroundService jest klasą bazową do implementowania długotrwałego IHostedServiceelementu .
Funkcja ExecuteAsync(CancellationToken) jest wywoływana w celu uruchomienia usługi w tle. Implementacja zwraca wartość Task reprezentującą cały okres istnienia usługi w tle. Żadne dalsze usługi nie są uruchamiane, dopóki funkcja ExecuteAsync nie stanie się asynchroniczna, na przykład przez wywołanie metody await
. Unikaj wykonywania długich, blokujących pracę inicjowania w programie ExecuteAsync
. Bloki hosta w stopAsync(CancellationToken) oczekujące na ExecuteAsync
zakończenie.
Token anulowania jest wyzwalany po wywołaniu wywołania IHostedService.StopAsync . Implementacja polecenia ExecuteAsync
powinna zakończyć się natychmiast, gdy token anulowania zostanie wyzwolony, aby bezpiecznie zamknąć usługę. W przeciwnym razie usługa niegracyjnie zamyka się po przekroczeniu limitu czasu zamknięcia. Aby uzyskać więcej informacji, zobacz sekcję interfejsu IHostedService.
Aby uzyskać więcej informacji, zobacz kod źródłowy usługi BackgroundService .
Czasowe zadania w tle
Zadanie w tle czasowe korzysta z klasy System.Threading.Timer . Czasomierz wyzwala metodę DoWork
zadania. Czasomierz jest wyłączony StopAsync
i usuwany po usunięciu kontenera usługi w systemie 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();
}
}
Element Timer nie czeka na ukończenie poprzednich DoWork
wykonań, więc pokazane podejście może nie być odpowiednie dla każdego scenariusza. Interlocked.Increment służy do przyrostowania licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie jest aktualizowanych executionCount
jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices
(Program.cs
) przy użyciu AddHostedService
metody rozszerzenia:
services.AddHostedService<TimedHostedService>();
Korzystanie z usługi o określonym zakresie w zadaniu w tle
Aby używać usług o określonym zakresie w ramach usługi BackgroundService, utwórz zakres. Domyślnie dla hostowanej usługi nie jest tworzony żaden zakres.
Zakres usługi zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWork
zwraca wartośćTask
. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork
. - Element ILogger jest wstrzykiwany do usługi.
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);
}
}
}
Usługa hostowana tworzy zakres, aby rozpoznać zakres usługi zadań w tle w celu wywołania jej DoWork
metody. DoWork
zwraca element , który jest oczekiwany w elemecie Task
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);
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle w kolejce
Kolejka zadań w tle jest oparta na platformie .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;
}
}
W poniższym QueueHostedService
przykładzie:
- Metoda
BackgroundProcessing
zwracaTask
element , który jest oczekiwany w plikuExecuteAsync
. - Zadania w tle w kolejce są usuwane z kolejki i wykonywane w pliku
BackgroundProcessing
. - Elementy robocze są oczekiwane przed zatrzymanie usługi w programie
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);
}
}
MonitorLoop
Usługa obsługuje kolejkowanie zadań dla hostowanej usługi za każdym razem, gdy w
klucz jest wybrany na urządzeniu wejściowym:
- Element
IBackgroundTaskQueue
jest wstrzykiwany doMonitorLoop
usługi. IBackgroundTaskQueue.QueueBackgroundWorkItem
element jest wywoływany w celu kolejkowania elementu roboczego.- Element roboczy symuluje długotrwałe zadanie w tle:
- Wykonywane są trzy 5-sekundowe opóźnienia (
Task.Delay
). - Instrukcja
try-catch
wychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Wykonywane są trzy 5-sekundowe opóźnienia (
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);
}
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
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
program jest uruchamiany w pliku Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchroniczne zadanie w tle z czasem
Poniższy kod tworzy asynchroniczne zadanie w tle:
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);
}
}
Dodatkowe zasoby
W ASP.NET Core zadania w tle można zaimplementować jako usługi hostowane. Hostowana usługa to klasa z logiką zadań w tle, która implementuje IHostedService interfejs. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane na czasomierzu.
- Hostowana usługa, która aktywuje usługę o określonym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle w kolejce, które są uruchamiane sekwencyjnie.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Szablon usługi procesu roboczego
Szablon usługi ASP.NET Core Worker Service stanowi punkt wyjścia do pisania długotrwałych aplikacji usługi. Aplikacja utworzona na podstawie szablonu usługi Worker Service określa zestaw SDK procesu roboczego w pliku projektu:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa procesu roboczego. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz pozycję Utwórz.
- W oknie dialogowym Tworzenie nowej usługi Procesu roboczego wybierz pozycję Utwórz.
Pakiet
Aplikacja oparta na szablonie usługi roboczej używa Microsoft.NET.Sdk.Worker
zestawu SDK i zawiera jawne odwołanie do pakietu Microsoft.Extensions.Hosting . Zobacz na przykład plik projektu przykładowej aplikacji (BackgroundTasksSample.csproj
).
W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web
zestawu SDK pakiet Microsoft.Extensions.Hosting odwołuje się niejawnie z udostępnionej platformy. Jawne odwołanie do pakietu w pliku projektu aplikacji nie jest wymagane.
Interfejs IHostedService
Interfejs IHostedService definiuje dwie metody dla obiektów zarządzanych przez hosta:
StartAsync
StartAsync
zawiera logikę uruchamiania zadania w tle. StartAsync
jest wywoływana przed:
- Potok przetwarzania żądań aplikacji jest skonfigurowany.
- Serwer jest uruchomiony i wyzwalany jest element IApplicationLifetime.ApplicationStarted .
Zachowanie domyślne można zmienić tak, aby usługa hostowana StartAsync
była uruchamiana po skonfigurowaniu potoku aplikacji i ApplicationStarted
jest wywoływana. Aby zmienić zachowanie domyślne, dodaj hostowaną usługę (VideosWatcher
w poniższym przykładzie) po wywołaniu metody 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
- Funkcja StopAsync(CancellationToken) jest wyzwalana, gdy host wykonuje bezproblemowe zamykanie.
StopAsync
zawiera logikę, która ma zakończyć zadanie w tle. Zaimplementuj IDisposable i finalizatory (destruktory) do usuwania wszystkich niezarządzanych zasobów.
Token anulowania ma domyślny pięć sekund limitu czasu, aby wskazać, że proces zamykania nie powinien już być wdziękny. Po zażądaniu anulowania w tokenie:
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsync
metodach powinny zostać zwrócone natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na ukończenie wszystkich zadań.
Jeśli aplikacja zostanie nieoczekiwanie zamknięta (na przykład proces aplikacji zakończy się niepowodzeniem), StopAsync
może nie zostać wywołana. W związku z tym wszelkie metody wywoływane lub wykonywane w programie StopAsync
operacje mogą nie wystąpić.
Aby przedłużyć domyślny limit czasu zamknięcia pięciu sekund, ustaw:
- ShutdownTimeout w przypadku korzystania z hosta ogólnego. Aby uzyskać więcej informacji, zobacz Host ogólny platformy ASP.NET Core.
- Ustawienie konfiguracji hosta limitu czasu zamknięcia podczas korzystania z hosta sieci Web. Aby uzyskać więcej informacji, zobacz ASP.NET Core Web Host.
Hostowana usługa jest aktywowana raz podczas uruchamiania aplikacji i bezpiecznie zamykana po zamknięciu aplikacji. Jeśli podczas wykonywania zadania w tle zostanie zgłoszony błąd, powinien zostać wywołany nawet wtedy, Dispose
gdy StopAsync
nie zostanie wywołany.
BackgroundService, klasa bazowa
BackgroundService jest klasą bazową do implementowania długotrwałego IHostedServiceelementu .
Funkcja ExecuteAsync(CancellationToken) jest wywoływana w celu uruchomienia usługi w tle. Implementacja zwraca wartość Task reprezentującą cały okres istnienia usługi w tle. Żadne dalsze usługi nie są uruchamiane, dopóki funkcja ExecuteAsync nie stanie się asynchroniczna, na przykład przez wywołanie metody await
. Unikaj wykonywania długich, blokujących pracę inicjowania w programie ExecuteAsync
. Bloki hosta w stopAsync(CancellationToken) oczekujące na ExecuteAsync
zakończenie.
Token anulowania jest wyzwalany po wywołaniu wywołania IHostedService.StopAsync . Implementacja polecenia ExecuteAsync
powinna zakończyć się natychmiast, gdy token anulowania zostanie wyzwolony, aby bezpiecznie zamknąć usługę. W przeciwnym razie usługa niegracyjnie zamyka się po przekroczeniu limitu czasu zamknięcia. Aby uzyskać więcej informacji, zobacz sekcję interfejsu IHostedService.
StartAsync
powinno być ograniczone do krótkich uruchomionych zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane do momentu StartAsync
ukończenia. Długotrwałe zadania należy umieścić w pliku ExecuteAsync
. Aby uzyskać więcej informacji, zobacz źródło usługi BackgroundService.
Czasowe zadania w tle
Zadanie w tle czasowe korzysta z klasy System.Threading.Timer . Czasomierz wyzwala metodę DoWork
zadania. Czasomierz jest wyłączony StopAsync
i usuwany po usunięciu kontenera usługi w systemie 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();
}
}
Element Timer nie czeka na ukończenie poprzednich DoWork
wykonań, więc pokazane podejście może nie być odpowiednie dla każdego scenariusza. Interlocked.Increment służy do przyrostowania licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie jest aktualizowanych executionCount
jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices
(Program.cs
) przy użyciu AddHostedService
metody rozszerzenia:
services.AddHostedService<TimedHostedService>();
Korzystanie z usługi o określonym zakresie w zadaniu w tle
Aby używać usług o określonym zakresie w ramach usługi BackgroundService, utwórz zakres. Domyślnie dla hostowanej usługi nie jest tworzony żaden zakres.
Zakres usługi zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWork
zwraca wartośćTask
. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork
. - Element ILogger jest wstrzykiwany do usługi.
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);
}
}
}
Usługa hostowana tworzy zakres, aby rozpoznać zakres usługi zadań w tle w celu wywołania jej DoWork
metody. DoWork
zwraca element , który jest oczekiwany w elemecie Task
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);
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle w kolejce
Kolejka zadań w tle jest oparta na platformie .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;
}
}
W poniższym QueueHostedService
przykładzie:
- Metoda
BackgroundProcessing
zwracaTask
element , który jest oczekiwany w plikuExecuteAsync
. - Zadania w tle w kolejce są usuwane z kolejki i wykonywane w pliku
BackgroundProcessing
. - Elementy robocze są oczekiwane przed zatrzymanie usługi w programie
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);
}
}
MonitorLoop
Usługa obsługuje kolejkowanie zadań dla hostowanej usługi za każdym razem, gdy w
klucz jest wybrany na urządzeniu wejściowym:
- Element
IBackgroundTaskQueue
jest wstrzykiwany doMonitorLoop
usługi. IBackgroundTaskQueue.QueueBackgroundWorkItem
element jest wywoływany w celu kolejkowania elementu roboczego.- Element roboczy symuluje długotrwałe zadanie w tle:
- Wykonywane są trzy 5-sekundowe opóźnienia (
Task.Delay
). - Instrukcja
try-catch
wychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Wykonywane są trzy 5-sekundowe opóźnienia (
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);
}
}
}
Usługi są zarejestrowane w IHostBuilder.ConfigureServices
programie (Program.cs
). Hostowana usługa jest zarejestrowana w metodzie AddHostedService
rozszerzenia:
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
program jest uruchamiany w pliku Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();