Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Przez Jeow Li Huan
Note
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.
Warning
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.
W ASP.NET Core zadania w tle można zaimplementować jako usługi hostowane. Hostowana usługa to klasa, która implementuje interfejs IHostedService i zawiera logikę zadań w tle. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane z użyciem zegara.
- Hostowana usługa, która aktywuje usługę o zdefiniowanym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle ustawione w kolejce, które uruchamiają się sekwencyjnie.
Szablon usługi Worker Service
Szablon ASP.NET Core Usługi Pracowniczej stanowi punkt wyjścia do pisania długo działających aplikacji serwisowych. Aplikacja utworzona na podstawie szablonu Worker Service wskazuje Worker SDK w swoim pliku projektu.
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- w programie Visual Studio
- .NET CLI (Interfejs wiersza polecenia .NET)
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa robocza. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym „Dodatkowe informacje”, wybierz „Framework”. Wybierz Utwórz.
Package
Aplikacja oparta na szablonie Usługi Roboczej używa zestawu SDK Microsoft.NET.Sdk.Worker i ma 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 SDK Microsoft.NET.Sdk.Web, pakiet Microsoft.Extensions.Hosting jest odwoływany niejawnie z wspólnej 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 zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane, dopóki StartAsync nie zostanie ukończone.
Wystąpienia usługi hostowanej są uruchamiane w kolejności, w jakiej zostały zarejestrowane w kontenerze iniekcji zależności, chyba że aplikacja włączy współbieżne uruchamianie przez ustawienie ServicesStartConcurrently na true:
builder.Services.Configure<HostOptions>(options =>
{
options.ServicesStartConcurrently = true;
});
StopAsync
-
Funkcja StopAsync(CancellationToken) jest wyzwalana, gdy host wykonuje bezproblemowe zamykanie.
StopAsynczawiera 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 być już kontrolowany. Po zażądaniu anulowania przez token.
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsyncpowinny zakończyć się natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na dokoń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 wywoływane metody lub przeprowadzane operacje w StopAsync mogą nie wystąpić.
Aby przedłużyć domyślny limit czasu zamknięcia 30 sekund, ustaw:
- ShutdownTimeout podczas korzystania z hosta generycznego. Aby uzyskać więcej informacji, zobacz Host ogólny .NET w 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.
Wystąpienia usług hostowanych zatrzymują się w odwrotnej kolejności do tej, w jakiej są zarejestrowane w kontenerze wstrzykiwania zależności, chyba że aplikacja włączy współbieżne zamykanie, ustawiając ServicesStopConcurrently na true:
builder.Services.Configure<HostOptions>(options =>
{
options.ServicesStopConcurrently = true;
});
BackgroundService, klasa bazowa
BackgroundService jest klasą bazową do implementowania długotrwałego IHostedService.
Funkcja ExecuteAsync(CancellationToken) jest wywoływana w puli wątków w celu uruchomienia usługi w tle. Implementacja zwraca wartość Task reprezentującą cały okres istnienia usługi w tle. Blokowanie hosta w StopAsync(CancellationToken) czekającego na zakończenie ExecuteAsync.
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 inicjalizacji w ExecuteAsync. Blokowanie hosta w StopAsync(CancellationToken) czekającego na zakończenie ExecuteAsync.
Token anulowania jest wyzwalany, gdy zostanie wywołany 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 .
Zadania w tle o zaplanowanym czasie
Zadanie w tle z ustalonym czasem 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 aż wcześniejsze wykonania DoWork się zakończą, więc przedstawione podejście może nie być odpowiednie dla każdego scenariusza.
Interlocked.Increment służy do inkrementacji licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie aktualizuje licznika jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices (Program.cs) przy użyciu metody rozszerzenia AddHostedService.
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.
Usługa zdefiniowana dla zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWorkzwraca wartośćTask. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork. - Do usługi wstrzykiwany jest element 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);
}
}
}
Usługa hostowana tworzy zakres umożliwiający rozwiązanie serwisu zadań w tle oraz wywołanie jego metody DoWork.
DoWork zwraca Task, który jest oczekiwany w 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle oczekujące na wykonanie
Kolejka zadań w tle jest oparta na programie .NET Framework 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
BackgroundProcessingzwracaTask, który jest oczekiwany wExecuteAsync. - Zadania w tle w kolejce są ściągane z kolejki i wykonywane w
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 klawisz w jest wybrany na urządzeniu wejściowym.
- Element
IBackgroundTaskQueuejest wstrzykiwany do usługiMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemjest wywoływany w celu kolejkowania zadania roboczego. - Element roboczy symuluje długotrwałe zadanie w tle:
- Proces wykonuje trzy 5-sekundowe opóźnienia (
Task.Delay). - Instrukcja
try-catchwychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Proces wykonuje 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
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 jest uruchamiany w Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchroniczne czasowe zadanie w tle
Poniższy kod tworzy asynchroniczne zadanie w tle z ustalonym czasem wykonania.
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Natywne AOT
Szablony usługi Roboczej obsługują natywną platformę .NET z wyprzedzeniem (AOT) z flagą --aot :
- w programie Visual Studio
- .NET CLI (Interfejs wiersza polecenia .NET)
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa robocza. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym Dodatkowe informacje:
- Wybierz framework.
- Zaznacz pole wyboru Włącz natywną publikację AOT.
- Wybierz 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, która implementuje interfejs IHostedService i zawiera logikę zadań w tle. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane z użyciem zegara.
- Hostowana usługa, która aktywuje usługę o zdefiniowanym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle ustawione w kolejce, które uruchamiają się sekwencyjnie.
Szablon usługi Worker Service
Szablon ASP.NET Core Usługi Pracowniczej stanowi punkt wyjścia do pisania długo działających aplikacji serwisowych. Aplikacja utworzona na podstawie szablonu Worker Service wskazuje Worker SDK w swoim pliku projektu.
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- w programie Visual Studio
- .NET CLI (Interfejs wiersza polecenia .NET)
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa robocza. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Dalej.
- W oknie dialogowym „Dodatkowe informacje”, wybierz „Framework”. Wybierz Utwórz.
Package
Aplikacja oparta na szablonie Usługi Roboczej używa zestawu SDK Microsoft.NET.Sdk.Worker i ma 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 SDK Microsoft.NET.Sdk.Web, pakiet Microsoft.Extensions.Hosting jest odwoływany niejawnie z wspólnej 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 zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane, dopóki StartAsync nie zostanie ukończone.
StopAsync
-
Funkcja StopAsync(CancellationToken) jest wyzwalana, gdy host wykonuje bezproblemowe zamykanie.
StopAsynczawiera 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 być już kontrolowany. Po zażądaniu anulowania przez token.
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsyncpowinny zakończyć się natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na dokoń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 wywoływane metody lub przeprowadzane operacje w StopAsync mogą nie wystąpić.
Aby przedłużyć domyślny limit czasu zamknięcia 30 sekund, ustaw:
- ShutdownTimeout podczas korzystania z hosta generycznego. Aby uzyskać więcej informacji, zobacz Host ogólny .NET w 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 IHostedService.
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 inicjalizacji w ExecuteAsync. Blokowanie hosta w StopAsync(CancellationToken) czekającego na zakończenie ExecuteAsync.
Token anulowania jest wyzwalany, gdy zostanie wywołany 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 .
Zadania w tle o zaplanowanym czasie
Zadanie w tle z ustalonym czasem 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 aż wcześniejsze wykonania DoWork się zakończą, więc przedstawione podejście może nie być odpowiednie dla każdego scenariusza.
Interlocked.Increment służy do inkrementacji licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie aktualizuje licznika jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices (Program.cs) przy użyciu metody rozszerzenia AddHostedService.
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.
Usługa zdefiniowana dla zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWorkzwraca wartośćTask. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork. - Do usługi wstrzykiwany jest element 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);
}
}
}
Usługa hostowana tworzy zakres umożliwiający rozwiązanie serwisu zadań w tle oraz wywołanie jego metody DoWork.
DoWork zwraca Task, który jest oczekiwany w 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle oczekujące na wykonanie
Kolejka zadań w tle jest oparta na programie .NET Framework 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
BackgroundProcessingzwracaTask, który jest oczekiwany wExecuteAsync. - Zadania w tle w kolejce są ściągane z kolejki i wykonywane w
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 klawisz w jest wybrany na urządzeniu wejściowym.
- Element
IBackgroundTaskQueuejest wstrzykiwany do usługiMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemjest wywoływany w celu kolejkowania zadania roboczego. - Element roboczy symuluje długotrwałe zadanie w tle:
- Proces wykonuje trzy 5-sekundowe opóźnienia (
Task.Delay). - Instrukcja
try-catchwychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Proces wykonuje 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
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 jest uruchamiany w Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Asynchroniczne czasowe zadanie w tle
Poniższy kod tworzy asynchroniczne zadanie w tle z ustalonym czasem wykonania.
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Dodatkowe zasoby
W ASP.NET Core zadania w tle można zaimplementować jako usługi hostowane. Hostowana usługa to klasa, która implementuje interfejs IHostedService i zawiera logikę zadań w tle. Ten artykuł zawiera trzy przykłady hostowanych usług:
- Zadanie w tle uruchamiane z użyciem zegara.
- Hostowana usługa, która aktywuje usługę o zdefiniowanym zakresie. Usługa o określonym zakresie może używać wstrzykiwania zależności (DI).
- Zadania w tle ustawione w kolejce, które uruchamiają się sekwencyjnie.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Szablon usługi Worker Service
Szablon ASP.NET Core Usługi Pracowniczej stanowi punkt wyjścia do pisania długo działających aplikacji serwisowych. Aplikacja utworzona na podstawie szablonu Worker Service wskazuje Worker SDK w swoim pliku projektu.
<Project Sdk="Microsoft.NET.Sdk.Worker">
Aby użyć szablonu jako podstawy dla aplikacji usług hostowanych:
- w programie Visual Studio
- .NET CLI (Interfejs wiersza polecenia .NET)
- Tworzenie nowego projektu.
- Wybierz pozycję Usługa robocza. Wybierz Dalej.
- Podaj nazwę projektu w polu Nazwa projektu lub zaakceptuj domyślną nazwę projektu. Wybierz Utwórz.
- W oknie dialogowym Tworzenie nowej usługi Worker wybierz pozycję Utwórz.
Package
Aplikacja oparta na szablonie Usługi Roboczej używa zestawu SDK Microsoft.NET.Sdk.Worker i ma 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 SDK Microsoft.NET.Sdk.Web, pakiet Microsoft.Extensions.Hosting jest odwoływany niejawnie z wspólnej 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 hostowana usługa StartAsync była uruchamiana po skonfigurowaniu potoku aplikacji i wywołaniu ApplicationStarted. 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.
StopAsynczawiera 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 pięć sekund, aby wskazać, że proces zamykania nie powinien już przebiegać łagodnie. Po zażądaniu anulowania przez token.
- Wszelkie pozostałe operacje w tle wykonywane przez aplikację powinny zostać przerwane.
- Wszystkie metody wywoływane w
StopAsyncpowinny zakończyć się natychmiast.
Jednak zadania nie są porzucane po żądaniu anulowania — obiekt wywołujący oczekuje na dokoń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 wywoływane metody lub przeprowadzane operacje w StopAsync mogą nie wystąpić.
Aby przedłużyć domyślny czas zamknięcia wynoszący pięć sekund, ustaw:
- ShutdownTimeout podczas korzystania z hosta generycznego. Aby uzyskać więcej informacji, zobacz Host ogólny .NET w 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 IHostedService.
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 inicjalizacji w ExecuteAsync. Blokowanie hosta w StopAsync(CancellationToken) czekającego na zakończenie ExecuteAsync.
Token anulowania jest wyzwalany, gdy zostanie wywołany 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 zadań, ponieważ hostowane usługi są uruchamiane sekwencyjnie, a żadne dalsze usługi nie są uruchamiane, dopóki StartAsync nie zostanie ukończone. Długotrwałe zadania należy umieścić w pliku ExecuteAsync. Aby uzyskać więcej informacji, zobacz źródło usługi BackgroundService.
Zadania w tle o zaplanowanym czasie
Zadanie w tle z ustalonym czasem 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 aż wcześniejsze wykonania DoWork się zakończą, więc przedstawione podejście może nie być odpowiednie dla każdego scenariusza.
Interlocked.Increment służy do inkrementacji licznika wykonywania jako operacji niepodzielnej, co gwarantuje, że wiele wątków nie aktualizuje licznika jednocześnie.
Usługa jest zarejestrowana w IHostBuilder.ConfigureServices (Program.cs) przy użyciu metody rozszerzenia AddHostedService.
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.
Usługa zdefiniowana dla zadań w tle zawiera logikę zadania w tle. W poniższym przykładzie:
- Usługa jest asynchroniczna. Metoda
DoWorkzwraca wartośćTask. W celach demonstracyjnych oczekuje się na opóźnienie dziesięciu sekund w metodzieDoWork. - Do usługi wstrzykiwany jest element 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);
}
}
}
Usługa hostowana tworzy zakres umożliwiający rozwiązanie serwisu zadań w tle oraz wywołanie jego metody DoWork.
DoWork zwraca Task, który jest oczekiwany w 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Zadania w tle oczekujące na wykonanie
Kolejka zadań w tle jest oparta na programie .NET Framework 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
BackgroundProcessingzwracaTask, który jest oczekiwany wExecuteAsync. - Zadania w tle w kolejce są ściągane z kolejki i wykonywane w
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 klawisz w jest wybrany na urządzeniu wejściowym.
- Element
IBackgroundTaskQueuejest wstrzykiwany do usługiMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemjest wywoływany w celu kolejkowania zadania roboczego. - Element roboczy symuluje długotrwałe zadanie w tle:
- Proces wykonuje trzy 5-sekundowe opóźnienia (
Task.Delay). - Instrukcja
try-catchwychwyci OperationCanceledException , jeśli zadanie zostanie anulowane.
- Proces wykonuje 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 (Program.cs). Hostowana usługa jest zarejestrowana za pomocą metody rozszerzenia AddHostedService.
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 jest uruchamiany w Program.Main:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();