Membuat Layanan Antrean
Layanan antrean adalah contoh yang bagus dari layanan yang berjalan lama, di mana item kerja dapat diantrekan dan dikerjakan secara berurutan karena item kerja sebelumnya selesai. Mengandalkan templat Layanan Pekerja, Anda membangun fungsionalitas baru di atas BackgroundService.
Dalam tutorial ini, Anda akan mempelajari cara:
- Membuat layanan antrean.
- Mendelegasikan pekerjaan ke antrean tugas.
- Daftarkan pendengar kunci konsol dari IHostApplicationLifetime peristiwa.
Tip
Semua kode sumber contoh "Pekerja di .NET" tersedia di Browser Sampel untuk diunduh. Untuk informasi selengkapnya, lihat Menelusuri sampel kode: Pekerja di .NET.
Prasyarat
- .NET 8.0 SDK atau yang lebih baru
- Lingkungan pengembangan terintegrasi .NET (IDE)
- Jangan ragu untuk menggunakan Visual Studio
Membuat proyek baru
Untuk membuat proyek Layanan Pekerja baru dengan Visual Studio, Anda akan memilih File>Proyek Baru>....Dari dialog Buat proyek baru untuk "Layanan Pekerja", dan pilih templat Layanan Pekerja. Jika Anda lebih suka menggunakan .NET CLI, buka terminal favorit Anda di direktori kerja. Jalankan dotnet new
perintah , dan ganti dengan nama proyek yang <Project.Name>
Anda inginkan.
dotnet new worker --name <Project.Name>
Untuk informasi selengkapnya tentang perintah proyek layanan pekerja baru .NET CLI, lihat dotnet pekerja baru.
Tip
Jika Anda menggunakan Visual Studio Code, Anda dapat menjalankan perintah .NET CLI dari terminal terintegrasi. Untuk informasi selengkapnya, lihat Visual Studio Code: Terminal Terintegrasi.
Membuat layanan antrean
Anda mungkin terbiasa dengan QueueBackgroundWorkItem(Func<CancellationToken,Task>) fungsionalitas dari System.Web.Hosting
namespace layanan.
Tip
Fungsionalitas System.Web
namespace layanan sengaja tidak di-port ke .NET, dan tetap eksklusif untuk .NET Framework. Untuk informasi selengkapnya, lihat Mulai menggunakan ASP.NET inkremental untuk ASP.NET migrasi Core.
Di .NET, untuk memodelkan layanan yang terinspirasi oleh QueueBackgroundWorkItem
fungsionalitas, mulailah dengan menambahkan IBackgroundTaskQueue
antarmuka ke proyek:
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Ada dua metode, satu yang mengekspos fungsionalitas antrean, dan satu lagi yang menghapus antrean item kerja yang sebelumnya diantrekan. Item kerja adalah Func<CancellationToken, ValueTask>
. Selanjutnya, tambahkan implementasi default ke proyek.
using System.Threading.Channels;
namespace App.QueueService;
public sealed class DefaultBackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public DefaultBackgroundTaskQueue(int capacity)
{
BoundedChannelOptions options = new(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
ArgumentNullException.ThrowIfNull(workItem);
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
Func<CancellationToken, ValueTask>? workItem =
await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
Implementasi sebelumnya bergantung pada Channel<T> sebagai antrean. disebut BoundedChannelOptions(Int32) dengan kapasitas eksplisit. Kapasitas harus ditetapkan berdasarkan beban aplikasi yang diharapkan dan jumlah utas bersamaan yang mengakses antrean. BoundedChannelFullMode.Wait menyebabkan panggilan ke untuk ChannelWriter<T>.WriteAsync mengembalikan tugas, yang hanya selesai ketika ruang tersedia. Yang menyebabkan backpressure, jika terlalu banyak penerbit/panggilan mulai terakumulasi.
Menulis ulang kelas Pekerja
Dalam contoh berikut QueueHostedService
:
- Metode
ProcessTaskQueueAsync
mengembalikan Task dalamExecuteAsync
. - Tugas latar belakang dalam antrean dihentikan antreannya dan dijalankan di
ProcessTaskQueueAsync
. - Item kerja ditunggu sebelum layanan berhenti di
StopAsync
.
Ganti kelas yang ada Worker
dengan kode C# berikut, dan ganti nama file menjadi QueueHostedService.cs.
namespace App.QueueService;
public sealed class QueuedHostedService(
IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("""
{Name} is running.
Tap W to add a work item to the
background queue.
""",
nameof(QueuedHostedService));
return ProcessTaskQueueAsync(stoppingToken);
}
private async Task ProcessTaskQueueAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
Func<CancellationToken, ValueTask>? workItem =
await taskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
catch (OperationCanceledException)
{
// Prevent throwing if stoppingToken was signaled
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred executing task work item.");
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
$"{nameof(QueuedHostedService)} 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.QueueBackgroundWorkItemAsync
dipanggil untuk mengantrekan item kerja.- Item kerja mensimulasikan tugas latar belakang yang berjalan lama:
- Tiga penundaan 5 detik dijalankan Delay.
- Pernyataan
try-catch
menjebak OperationCanceledException jika tugas dibatalkan.
namespace App.QueueService;
public sealed class MonitorLoop(
IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
private readonly CancellationToken _cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
{
logger.LogInformation($"{nameof(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(BuildWorkItemAsync);
}
}
}
private async ValueTask BuildWorkItemAsync(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid();
logger.LogInformation("Queued work item {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 work item {Guid} is running. {DelayLoop}/3", guid, delayLoop);
}
if (delayLoop is 3)
{
logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Ganti konten yang ada Program
dengan kode C# berikut:
using App.QueueService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<MonitorLoop>();
builder.Services.AddHostedService<QueuedHostedService>();
builder.Services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(builder.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new DefaultBackgroundTaskQueue(queueCapacity);
});
IHost host = builder.Build();
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
host.Run();
Layanan terdaftar di (Program.cs). Layanan yang dihosting terdaftar dengan AddHostedService
metode ekstensi. MonitorLoop
dimulai dalam pernyataan tingkat atas Program.cs :
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Untuk informasi selengkapnya tentang mendaftarkan layanan, lihat Injeksi dependensi di .NET.
Memverifikasi fungsionalitas layanan
Untuk menjalankan aplikasi dari Visual Studio, pilih F5 atau pilih opsi menu Debug>Mulai Debugging . Jika Anda menggunakan .NET CLI, jalankan dotnet run
perintah dari direktori kerja:
dotnet run
Untuk informasi selengkapnya tentang perintah jalankan .NET CLI, lihat dotnet run.
Ketika diminta masukkan w
(atau W
) setidaknya sekali untuk mengantrekan item kerja yang ditimulasi, seperti yang ditunjukkan dalam contoh output:
info: App.QueueService.MonitorLoop[0]
MonitorAsync loop is starting.
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is running.
Tap W to add a work item to the background queue.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\queue-service
winfo: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is starting.
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 1/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 2/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 3/3
info: App.QueueService.MonitorLoop[0]
Queued Background Task 8453f845-ea4a-4bcb-b26e-c76c0d89303e is complete.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is stopping.
Jika menjalankan aplikasi dari dalam Visual Studio, pilih Debug>Hentikan Penelusuran Kesalahan.... Atau, pilih Ctrl + C dari jendela konsol untuk memberi sinyal pembatalan.