0Pricing
C# Academy · Lekcja

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 shutdown

Praktyczny 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 zamykaniu
  • BackgroundService: pętla ExecuteAsync — do ciągłej pracy w tle
  • W usługach działających w tle należy zawsze używać IServiceScopeFactory dla 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

  1. IHostedService i BackgroundService
  2. Projekty Worker Service
  3. Zadania okresowe i timery
  4. Zaplanowane zadania Quartz.NET
← Powrót do C# Academy