Tugas latar belakang dengan layanan yang dihosting di ASP.NET Core
Oleh Jeow Li Huan
Dalam 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 ASP.NET Core Worker 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 mendefinisikan dua metode untuk objek yang dikelola oleh host:
StartAsync
StartAsync(CancellationToken) berisi logika untuk memulai tugas latar belakang. StartAsync dipanggil sebelum:
- Alur pemrosesan permintaan aplikasi dikonfigurasi.
- Server dimulai dan IApplicationLifetime.ApplicationStarted dipicu.
StartAsync harus terbatas pada 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 dengan baik.
StopAsyncberisi logika untuk mengakhiri tugas latar belakang. Menerapkan IDisposable dan menyelesaikan (destruktor) untuk membuang sumber daya yang tidak dikelola.
Token pembatalan memiliki batas waktu default lima detik untuk menunjukkan bahwa proses pematian 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
StopAsyncharus segera kembali.
Namun, tugas tidak ditinggalkan setelah pembatalan diminta—pemanggil 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 mungkin StopAsync tidak terjadi.
Untuk memperpanjang batas waktu matikan lima detik default, atur:
- ShutdownTimeout saat menggunakan Host Generik. Untuk informasi selengkapnya, lihat Host Generik .NET 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 saat startup 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 pemblokiran 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 dimatikan secara tidak sengaja 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>();
Mengkonsumsi 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 tercakup berisi logika tugas latar belakang. Lihat contoh berikut:
- Layanan ini asinkron. Metode mengembalikan
DoWorkTask. 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 tercakup untuk memanggil metodenya DoWork . DoWorkTaskmengembalikan , 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
BackgroundProcessingmengembalikanTask, yang ditunggu dalamExecuteAsync. - Tugas latar belakang dalam antrean dihapus antrean 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:
IBackgroundTaskQueuedisuntikkan keMonitorLoopdalam layanan.IBackgroundTaskQueue.QueueBackgroundWorkItemdipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan (
Task.Delay). - Pernyataan
try-catchmenjebak 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();
Sumber Daya Tambahan:
Dalam 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.
Menampilkan 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 mendefinisikan dua metode untuk objek yang dikelola oleh host:
StartAsync
StartAsync berisi logika untuk memulai tugas latar belakang. StartAsync dipanggil sebelum:
- 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 dengan baik.
StopAsyncberisi logika untuk mengakhiri tugas latar belakang. Menerapkan IDisposable dan menyelesaikan (destruktor) untuk membuang sumber daya yang tidak terkelola.
Token pembatalan memiliki batas waktu default lima 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
StopAsyncharus segera dikembalikan.
Namun, tugas tidak ditinggalkan 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 matikan lima detik default, atur:
- ShutdownTimeout saat menggunakan Host Generik. Untuk informasi selengkapnya, lihat Host Generik .NET 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 pemblokiran yang panjang di ExecuteAsync. Host memblokir di StopAsync(CancellationToken) yang menunggu ExecuteAsync 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 secara tidak sah pada waktu matikan. Untuk informasi selengkapnya, lihat bagian antarmuka IHostedService .
StartAsync harus terbatas pada 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>();
Mengkonsumsi 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 tercakup berisi logika tugas latar belakang. Lihat contoh berikut:
- Layanan ini asinkron. Metode mengembalikan
DoWorkTask. 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 tercakup untuk memanggil metodenya DoWork . DoWorkTaskmengembalikan , 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 antrean
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
BackgroundProcessingmengembalikanTask, yang ditunggu diExecuteAsync. - Tugas latar belakang dalam antrean dihapus dari antrean 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:
IBackgroundTaskQueuedisuntikkan keMonitorLoopdalam layanan.IBackgroundTaskQueue.QueueBackgroundWorkItemdipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan (
Task.Delay). - Pernyataan
try-catchmenjebak 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 dalam Program.Main:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();