Tugas latar belakang dengan layanan yang dihosting di ASP.NET Core
Oleh Jeow Li Huan
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:
- Tugas latar belakang yang berjalan pada timer.
- Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
- Tugas latar belakang antrean yang berjalan secara berurutan.
Templat Layanan Pekerja
Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:
- Buat proyek baru.
- Pilih Layanan Pekerja. Pilih Selanjutnya.
- Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
- Dalam dialog Informasi tambahan, Pilih Kerangka Kerja. Pilih Buat.
Paket
Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker
SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj
).
Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web
SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.
Antarmuka IHostedService
Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:
StartAsync
StartAsync(CancellationToken) berisi logika untuk memulai tugas latar belakang. StartAsync
dipanggil sebelumnya:
- Alur pemrosesan permintaan aplikasi dikonfigurasi.
- Server dimulai dan IApplicationLifetime.ApplicationStarted dipicu.
StartAsync
harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync
eksekusi hingga selesai.
StopAsync
- StopAsync(CancellationToken) dipicu saat host melakukan pematian yang anggun.
StopAsync
berisi logika untuk mengakhiri tugas latar belakang. Menerapkan IDisposable dan menyelesaikan (destruktor) untuk membuang sumber daya yang tidak dikelola.
Token pembatalan memiliki batas waktu default 30 detik untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:
- Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
- Metode apa pun yang dipanggil
StopAsync
harus segera kembali.
Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.
Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync
mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync
mungkin tidak terjadi.
Untuk memperpanjang batas waktu mati 30 detik default, atur:
- ShutdownTimeout saat menggunakan Host Generik. Untuk informasi lebih lanjut, lihat .NET Generic Host di ASP.NET Core.
- Matikan pengaturan konfigurasi host batas waktu saat menggunakan Web Host. Untuk informasi selengkapnya, lihat ASP.NET Core Web Host.
Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose
harus dipanggil meskipun StopAsync
tidak dipanggil.
Kelas dasar BackgroundService
BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.
ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await
. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync
. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync
untuk selesai.
Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync
Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.
Untuk informasi selengkapnya, lihat kode sumber BackgroundService .
Tugas latar belakang berwaktu
Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork
. Timer dinonaktifkan dan StopAsync
dibuang ketika kontainer layanan dibuang pada 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();
}
}
Timer Tidak menunggu eksekusi DoWork
sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount
secara bersamaan.
Layanan ini terdaftar di IHostBuilder.ConfigureServices
(Program.cs
) dengan AddHostedService
metode ekstensi:
services.AddHostedService<TimedHostedService>();
Menggunakan layanan tercakup dalam tugas latar belakang
Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.
Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:
- Layanan ini asinkron. Metode mengembalikan
DoWork
Task
. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
- Disuntikkan ILogger ke dalam layanan.
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);
}
}
}
Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork
. DoWork
Task
mengembalikan , yang ditunggu dalam 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);
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tugas latar belakang yang diantrekan
Antrean tugas latar belakang didasarkan pada .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;
}
}
Dalam contoh berikut QueueHostedService
:
- Metode mengembalikan
BackgroundProcessing
Task
, yang ditunggu dalamExecuteAsync
. - Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di
BackgroundProcessing
. - Item kerja ditunggu sebelum layanan berhenti di
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
Layanan menangani tugas antrean untuk layanan yang dihosting setiap kali w
kunci dipilih pada perangkat input:
IBackgroundTaskQueue
disuntikkan keMonitorLoop
dalam layanan.IBackgroundTaskQueue.QueueBackgroundWorkItem
dipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan (
Task.Delay
). - Pernyataan
try-catch
menjebak OperationCanceledException jika tugas dibatalkan.
- Tiga penundaan 5 detik dijalankan (
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);
}
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
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
dimulai di Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Tugas latar belakang berwaktu asinkron
Kode berikut membuat tugas latar belakang berwaktu asinkron:
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);
}
}
AOT Asli
Templat Layanan Pekerja mendukung .NET native ahead-of-time (AOT) dengan --aot
bendera:
- Buat proyek baru.
- Pilih Layanan Pekerja. Pilih Selanjutnya.
- Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
- Dalam dialog Informasi tambahan:
- Pilih Kerangka Kerja.
- Centang kotak centang Aktifkan penerbitan AOT Asli.
- Pilih Buat.
Opsi AOT menambahkan <PublishAot>true</PublishAot>
ke file proyek:
<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>
Sumber Daya Tambahan:
Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:
- Tugas latar belakang yang berjalan pada timer.
- Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
- Tugas latar belakang antrean yang berjalan secara berurutan.
Templat Layanan Pekerja
Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:
- Buat proyek baru.
- Pilih Layanan Pekerja. Pilih Selanjutnya.
- Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Selanjutnya.
- Dalam dialog Informasi tambahan, Pilih Kerangka Kerja. Pilih Buat.
Paket
Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker
SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj
).
Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web
SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.
Antarmuka IHostedService
Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:
StartAsync
StartAsync(CancellationToken) berisi logika untuk memulai tugas latar belakang. StartAsync
dipanggil sebelumnya:
- Alur pemrosesan permintaan aplikasi dikonfigurasi.
- Server dimulai dan IApplicationLifetime.ApplicationStarted dipicu.
StartAsync
harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync
eksekusi hingga selesai.
StopAsync
- StopAsync(CancellationToken) dipicu saat host melakukan pematian yang anggun.
StopAsync
berisi logika untuk mengakhiri tugas latar belakang. Menerapkan IDisposable dan menyelesaikan (destruktor) untuk membuang sumber daya yang tidak dikelola.
Token pembatalan memiliki batas waktu default 30 detik untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:
- Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
- Metode apa pun yang dipanggil
StopAsync
harus segera kembali.
Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.
Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync
mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync
mungkin tidak terjadi.
Untuk memperpanjang batas waktu mati 30 detik default, atur:
- ShutdownTimeout saat menggunakan Host Generik. Untuk informasi lebih lanjut, lihat .NET Generic Host di ASP.NET Core.
- Matikan pengaturan konfigurasi host batas waktu saat menggunakan Web Host. Untuk informasi selengkapnya, lihat ASP.NET Core Web Host.
Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose
harus dipanggil meskipun StopAsync
tidak dipanggil.
Kelas dasar BackgroundService
BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.
ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await
. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync
. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync
untuk selesai.
Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync
Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.
Untuk informasi selengkapnya, lihat kode sumber BackgroundService .
Tugas latar belakang berwaktu
Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork
. Timer dinonaktifkan dan StopAsync
dibuang ketika kontainer layanan dibuang pada 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();
}
}
Timer Tidak menunggu eksekusi DoWork
sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount
secara bersamaan.
Layanan ini terdaftar di IHostBuilder.ConfigureServices
(Program.cs
) dengan AddHostedService
metode ekstensi:
services.AddHostedService<TimedHostedService>();
Menggunakan layanan tercakup dalam tugas latar belakang
Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.
Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:
- Layanan ini asinkron. Metode mengembalikan
DoWork
Task
. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
- Disuntikkan ILogger ke dalam layanan.
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);
}
}
}
Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork
. DoWork
Task
mengembalikan , yang ditunggu dalam 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);
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tugas latar belakang yang diantrekan
Antrean tugas latar belakang didasarkan pada .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;
}
}
Dalam contoh berikut QueueHostedService
:
- Metode mengembalikan
BackgroundProcessing
Task
, yang ditunggu dalamExecuteAsync
. - Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di
BackgroundProcessing
. - Item kerja ditunggu sebelum layanan berhenti di
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
Layanan menangani tugas antrean untuk layanan yang dihosting setiap kali w
kunci dipilih pada perangkat input:
IBackgroundTaskQueue
disuntikkan keMonitorLoop
dalam layanan.IBackgroundTaskQueue.QueueBackgroundWorkItem
dipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan (
Task.Delay
). - Pernyataan
try-catch
menjebak OperationCanceledException jika tugas dibatalkan.
- Tiga penundaan 5 detik dijalankan (
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);
}
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
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
dimulai di Program.cs
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Tugas latar belakang berwaktu asinkron
Kode berikut membuat tugas latar belakang berwaktu asinkron:
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);
}
}
Sumber Daya Tambahan:
Di ASP.NET Core, tugas latar belakang dapat diimplementasikan sebagai layanan yang dihosting. Layanan yang dihosting adalah kelas dengan logika tugas latar belakang yang mengimplementasikan IHostedService antarmuka. Artikel ini menyediakan tiga contoh layanan yang dihosting:
- Tugas latar belakang yang berjalan pada timer.
- Layanan yang dihosting yang mengaktifkan layanan terlingkup. Layanan tercakup dapat menggunakan injeksi dependensi (DI).
- Tugas latar belakang antrean yang berjalan secara berurutan.
Melihat atau mengunduh kode sampel (cara mengunduh)
Templat Layanan Pekerja
Templat Layanan Pekerja Inti ASP.NET menyediakan titik awal untuk menulis aplikasi layanan yang berjalan lama. Aplikasi yang dibuat dari templat Layanan Pekerja menentukan SDK Pekerja dalam file proyeknya:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Untuk menggunakan templat sebagai dasar untuk aplikasi layanan yang dihosting:
- Buat proyek baru.
- Pilih Layanan Pekerja. Pilih Selanjutnya.
- Berikan nama proyek di bidang Nama proyek atau terima nama proyek default. Pilih Buat.
- Dalam dialog Buat layanan Pekerja baru, pilih Buat.
Paket
Aplikasi berdasarkan templat Layanan Pekerja menggunakan Microsoft.NET.Sdk.Worker
SDK dan memiliki referensi paket eksplisit ke paket Microsoft.Extensions.Hosting . Misalnya, lihat file proyek aplikasi sampel (BackgroundTasksSample.csproj
).
Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web
SDK, paket Microsoft.Extensions.Hosting dirujuk secara implisit dari kerangka kerja bersama. Referensi paket eksplisit dalam file proyek aplikasi tidak diperlukan.
Antarmuka IHostedService
Antarmuka IHostedService menentukan dua metode untuk objek yang dikelola oleh host:
StartAsync
StartAsync
berisi logika untuk memulai tugas latar belakang. StartAsync
dipanggil sebelumnya:
- Alur pemrosesan permintaan aplikasi dikonfigurasi.
- Server dimulai dan IApplicationLifetime.ApplicationStarted dipicu.
Perilaku default dapat diubah sehingga layanan StartAsync
yang dihosting berjalan setelah alur aplikasi dikonfigurasi dan ApplicationStarted
dipanggil. Untuk mengubah perilaku default, tambahkan layanan yang dihosting (VideosWatcher
dalam contoh berikut) setelah memanggil ConfigureWebHostDefaults
:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddHostedService<VideosWatcher>();
});
}
StopAsync
- StopAsync(CancellationToken) dipicu saat host melakukan pematian yang anggun.
StopAsync
berisi logika untuk mengakhiri tugas latar belakang. Menerapkan IDisposable dan menyelesaikan (destruktor) untuk membuang sumber daya yang tidak dikelola.
Token pembatalan memiliki batas waktu lima detik default untuk menunjukkan bahwa proses matikan tidak boleh lagi anggun. Ketika pembatalan diminta pada token:
- Setiap operasi latar belakang yang tersisa yang dilakukan aplikasi harus dibatalkan.
- Metode apa pun yang dipanggil
StopAsync
harus segera kembali.
Namun, tugas tidak diabaikan setelah pembatalan diminta—penelepon menunggu semua tugas selesai.
Jika aplikasi dimatikan secara tiba-tiba (misalnya, proses aplikasi gagal), StopAsync
mungkin tidak dipanggil. Oleh karena itu, metode apa pun yang disebut atau operasi yang dilakukan StopAsync
mungkin tidak terjadi.
Untuk memperpanjang batas waktu mati lima detik default, atur:
- ShutdownTimeout saat menggunakan Host Generik. Untuk informasi lebih lanjut, lihat .NET Generic Host di ASP.NET Core.
- Matikan pengaturan konfigurasi host batas waktu saat menggunakan Web Host. Untuk informasi selengkapnya, lihat ASP.NET Core Web Host.
Layanan yang dihosting diaktifkan sekali pada pengaktifan aplikasi dan dimatikan dengan lancar saat aplikasi dimatikan. Jika kesalahan dilemparkan selama eksekusi tugas latar belakang, Dispose
harus dipanggil meskipun StopAsync
tidak dipanggil.
Kelas dasar BackgroundService
BackgroundService adalah kelas dasar untuk menerapkan jangka panjang IHostedService.
ExecuteAsync(CancellationToken) dipanggil untuk menjalankan layanan latar belakang. Implementasi mengembalikan Task yang mewakili seluruh masa pakai layanan latar belakang. Tidak ada layanan lebih lanjut yang dimulai sampai ExecuteAsync menjadi asinkron, seperti dengan memanggil await
. Hindari melakukan pekerjaan inisialisasi yang panjang dan memblokir di ExecuteAsync
. Blok host di StopAsync(CancellationToken) menunggu ExecuteAsync
untuk selesai.
Token pembatalan dipicu ketika IHostedService.StopAsync dipanggil. Implementasi ExecuteAsync
Anda harus segera selesai ketika token pembatalan diaktifkan untuk mematikan layanan dengan baik. Jika tidak, layanan akan dimatikan dengan tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService.
StartAsync
harus dibatasi untuk tugas yang berjalan singkat karena layanan yang dihosting dijalankan secara berurutan, dan tidak ada layanan lebih lanjut yang dimulai sampai StartAsync
eksekusi hingga selesai. Tugas yang berjalan lama harus ditempatkan di ExecuteAsync
. Untuk informasi selengkapnya, lihat sumber ke BackgroundService.
Tugas latar belakang berwaktu
Tugas latar belakang berwaktu menggunakan kelas System.Threading.Timer . Timer memicu metode tugas DoWork
. Timer dinonaktifkan dan StopAsync
dibuang ketika kontainer layanan dibuang pada 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();
}
}
Timer Tidak menunggu eksekusi DoWork
sebelumnya selesai, sehingga pendekatan yang ditampilkan mungkin tidak cocok untuk setiap skenario. Interlocked.Increment digunakan untuk menaikkan penghitung eksekusi sebagai operasi atomik, yang memastikan bahwa beberapa utas tidak diperbarui executionCount
secara bersamaan.
Layanan ini terdaftar di IHostBuilder.ConfigureServices
(Program.cs
) dengan AddHostedService
metode ekstensi:
services.AddHostedService<TimedHostedService>();
Menggunakan layanan tercakup dalam tugas latar belakang
Untuk menggunakan layanan terlingkup dalam BackgroundService, buat cakupan. Tidak ada cakupan yang dibuat untuk layanan yang dihosting secara default.
Layanan tugas latar belakang yang tercakup berisi logika tugas latar belakang. Dalam contoh berikut:
- Layanan ini asinkron. Metode mengembalikan
DoWork
Task
. Untuk tujuan demonstrasi, penundaan sepuluh detik ditunggu dalam metode .DoWork
- Disuntikkan ILogger ke dalam layanan.
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);
}
}
}
Layanan yang dihosting membuat cakupan untuk menyelesaikan layanan tugas latar belakang yang tercakup untuk memanggil metodenya DoWork
. DoWork
Task
mengembalikan , yang ditunggu dalam 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);
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tugas latar belakang yang diantrekan
Antrean tugas latar belakang didasarkan pada .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;
}
}
Dalam contoh berikut QueueHostedService
:
- Metode mengembalikan
BackgroundProcessing
Task
, yang ditunggu dalamExecuteAsync
. - Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di
BackgroundProcessing
. - Item kerja ditunggu sebelum layanan berhenti di
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
Layanan menangani tugas antrean untuk layanan yang dihosting setiap kali w
kunci dipilih pada perangkat input:
IBackgroundTaskQueue
disuntikkan keMonitorLoop
dalam layanan.IBackgroundTaskQueue.QueueBackgroundWorkItem
dipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan (
Task.Delay
). - Pernyataan
try-catch
menjebak OperationCanceledException jika tugas dibatalkan.
- Tiga penundaan 5 detik dijalankan (
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);
}
}
}
Layanan terdaftar di IHostBuilder.ConfigureServices
(Program.cs
). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi:
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
dimulai di Program.Main
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Sumber Daya Tambahan:
ASP.NET Core