IHostedService i BackgroundService
Implementuj IHostedService oraz abstrakcyjną klasę BackgroundService, aby uruchamiać zadania w tle w hoście .NET.
IHostedService i BackgroundService to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Czym są usługi w tle?
Usługi w tle wykonują długotrwałe zadania równolegle z aplikacją ASP.NET Core — przetwarzają kolejki, uruchamiają zaplanowane zadania lub monitorują zasoby. .NET udostępnia dwa interfejsy: IHostedService (minimalny) oraz abstrakcyjną klasę BackgroundService (przystosowaną do pracy w pętli).
Interfejs IHostedService
IHostedService ma dwie metody: StartAsync (wywoływaną podczas uruchamiania hosta) oraz StopAsync (wywoływaną podczas kontrolowanego zamykania). Jest idealny do jednorazowych zadań wykonywanych przy uruchamianiu i zamykaniu.
public class DatabaseMigratorService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public DatabaseMigratorService(IServiceScopeFactory sf) => _scopeFactory = sf;
public async Task StartAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync(ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigratorService>();BackgroundService: długotrwałe pętle
BackgroundService to abstrakcyjna klasa implementująca IHostedService i udostępniająca metodę ExecuteAsync(CancellationToken) — metodę działającą przez cały czas pracy aplikacji.
public class HeartbeatService : BackgroundService
{
private readonly ILogger<HeartbeatService> _logger;
public HeartbeatService(ILogger<HeartbeatService> l) => _logger = l;
protected override async Task ExecuteAsync(CancellationToken ct)
{
_logger.LogInformation("Heartbeat service started");
while (!ct.IsCancellationRequested)
{
_logger.LogInformation("Heartbeat: {Time}", DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
_logger.LogInformation("Heartbeat service stopping");
}
}
builder.Services.AddHostedService<HeartbeatService>();Korzystanie z usług Scoped
Usługi działające w tle są Singletonami. Aby korzystać z usług Scoped (takich jak DbContext), należy zawsze utworzyć nowy zakres dla każdej jednostki pracy za pomocą IServiceScopeFactory.
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<DataSyncService> _log;
public DataSyncService(IServiceScopeFactory f, ILogger<DataSyncService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
using (var scope = _factory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await SyncDataAsync(db, ct);
} // scope and DbContext disposed here
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}Kontrolowane zamykanie
Podczas zamykania host wywołuje token anulowania. Metoda ExecuteAsync powinna go obserwować i wykonać sprzątanie. Host domyślnie czeka do 5 sekund, zanim wymusi zatrzymanie.
protected override async Task ExecuteAsync(CancellationToken ct)
{
try
{
while (!ct.IsCancellationRequested)
{
await DoWorkAsync(ct);
await Task.Delay(1000, ct);
}
}
catch (OperationCanceledException)
{
// Normal shutdown — not an error
_logger.LogInformation("Service stopping due to cancellation");
}
finally
{
// Cleanup
await CleanupAsync();
}
}Kolejka z użyciem Channel
Typowy wzorzec polega na tym, że usługa działająca w tle odczytuje elementy z kolejki Channel, a endpointy HTTP umieszczają w niej zadania. Rozdziela to obsługę żądań od przetwarzania zadań.
public class BackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ValueTask QueueAsync(Func<CancellationToken, Task> job)
=> _queue.Writer.WriteAsync(job);
public IAsyncEnumerable<Func<CancellationToken, Task>> DequeueAllAsync(
CancellationToken ct)
=> _queue.Reader.ReadAllAsync(ct);
}
// Register and use:
builder.Services.AddSingleton<BackgroundTaskQueue>();
builder.Services.AddHostedService<QueueProcessorService>();Obsługa błędów w usługach działających w tle
Nieobsłużone wyjątki w metodzie ExecuteAsync mogą po cichu zatrzymać usługę. Należy umieścić główną pętlę w bloku try/catch i rejestrować błędy, aby usługa działała dalej mimo pojedynczych niepowodzeń.
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await ProcessNextBatchAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
break; // normal shutdown
}
catch (Exception ex)
{
_logger.LogError(ex, "Background processing error. Retrying in 5s.");
await Task.Delay(5000, ct); // backoff before retry
}
}
}StopAsync i limit czasu zamykania
Należy przesłonić metodę StopAsync, aby wykonać sprzątanie. Host przekazuje token zamykania — jeśli sprzątanie potrwa dłużej niż określony limit, proces zostanie bezwarunkowo zakończony.
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service is stopping...");
// Signal internal work to stop
_internalCts.Cancel();
// Wait for the Execute loop to complete (up to timeout)
await base.StopAsync(stoppingToken);
_logger.LogInformation("Service stopped");
}
// Increase the shutdown timeout if needed:
builder.Services.Configure<HostOptions>(opt =>
opt.ShutdownTimeout = TimeSpan.FromSeconds(30));Wiele usług działających w tle
Można zarejestrować wiele usług hostowanych — wszystkie będą działać równolegle. Host uruchamia je w kolejności rejestracji, a zatrzymuje w odwrotnej kolejności.
builder.Services.AddHostedService<DatabaseMigratorService>(); // startup task
builder.Services.AddHostedService<MetricsCollectorService>(); // continuous
builder.Services.AddHostedService<EmailNotificationService>(); // continuous
builder.Services.AddHostedService<CacheWarmupService>(); // startup task
// All run concurrently after app start
// Stopped in reverse registration order on shutdownPraktyczny przykład: procesor zamówień
Usługa działająca w tle przetwarzająca oczekujące zamówienia z kolejki w bazie danych, z prawidłowym zarządzaniem zakresami i obsługą błędów.
public class OrderProcessorService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<OrderProcessorService> _log;
public OrderProcessorService(IServiceScopeFactory f, ILogger<OrderProcessorService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
using var scope = _factory.CreateScope();
var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
int count = await processor.ProcessPendingAsync(ct);
_log.LogInformation("Processed {Count} orders", count);
}
catch (Exception ex) when (!ct.IsCancellationRequested)
{
_log.LogError(ex, "Order processing failed");
}
await Task.Delay(TimeSpan.FromSeconds(10), ct);
}
}
}Szybkie sprawdzenie
Dlaczego usługi działające w tle muszą używać IServiceScopeFactory zamiast bezpośredniego wstrzykiwania DbContext?
Podsumowanie: IHostedService i BackgroundService
Najważniejsze informacje:
IHostedService: StartAsync/StopAsync — do zadań wykonywanych przy uruchamianiu i zamykaniuBackgroundService: pętla ExecuteAsync — do ciągłej pracy w tle- W usługach działających w tle należy zawsze używać
IServiceScopeFactorydla zależności Scoped - Dla kontrolowanego zamykania należy obserwować token anulowania przy każdym await
- Ciało pętli należy umieścić w bloku try/catch, aby błędy nie kończyły działania usługi
- Jeśli sprzątanie wymaga więcej niż 5 sekund, należy użyć
HostOptions.ShutdownTimeout
Często zadawane pytania
Czy lekcja „IHostedService i BackgroundService” jest bezpłatna?
Tak — pełny tekst „IHostedService i BackgroundService” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „IHostedService i BackgroundService”?
Implementuj IHostedService oraz abstrakcyjną klasę BackgroundService, aby uruchamiać zadania w tle w hoście .NET. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „IHostedService i BackgroundService”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- IHostedService i BackgroundService
- Projekty Worker Service
- Zadania okresowe i timery
- Zaplanowane zadania Quartz.NET