From f02ae3398cc30bc64dbf1972a3a3f41e36b3f415 Mon Sep 17 00:00:00 2001 From: TheDayIsMyEnemy Date: Wed, 3 Jul 2024 20:28:16 +0300 Subject: [PATCH] Update Hosted Service Sample to .net8.0 --- .../BackgroundTasksSample.csproj | 14 +++ .../8.0/BackgroundTasksSample/Program.cs | 37 ++++++++ .../8.0/BackgroundTasksSample/README.md | 9 ++ .../Services/BackgroundTaskQueue.cs | 55 ++++++++++++ .../ConsumeScopedServiceHostedService.cs | 56 ++++++++++++ .../Services/MonitorLoop.cs | 85 +++++++++++++++++++ .../Services/QueuedHostedService.cs | 60 +++++++++++++ .../Services/ScopedProcessingService.cs | 37 ++++++++ .../Services/TimedHostedService.cs | 54 ++++++++++++ .../appsettings.Development.json | 9 ++ .../BackgroundTasksSample/appsettings.json | 9 ++ 11 files changed, 425 insertions(+) create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/BackgroundTasksSample.csproj create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Program.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/README.md create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/BackgroundTaskQueue.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ConsumeScopedServiceHostedService.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/MonitorLoop.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/QueuedHostedService.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ScopedProcessingService.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/TimedHostedService.cs create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.Development.json create mode 100644 aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.json diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/BackgroundTasksSample.csproj b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/BackgroundTasksSample.csproj new file mode 100644 index 000000000000..cc6c60cb036b --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/BackgroundTasksSample.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + dotnet-BackgroundTasksSample-E6370564-EEA4-467C-AC17-5BDD716D3C4B + BackgroundTasksSample + enable + + + + + + + diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Program.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Program.cs new file mode 100644 index 000000000000..8f39847662f8 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Program.cs @@ -0,0 +1,37 @@ +using BackgroundTasksSample.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +using var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + #region snippet3 + services.AddSingleton(); + services.AddHostedService(); + services.AddSingleton(ctx => + { + if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity)) + queueCapacity = 100; + return new BackgroundTaskQueue(queueCapacity); + }); + #endregion + + #region snippet1 + services.AddHostedService(); + #endregion + + #region snippet2 + services.AddHostedService(); + services.AddScoped(); + #endregion + }) + .Build(); + +await host.StartAsync(); + +#region snippet4 +var monitorLoop = host.Services.GetRequiredService(); +monitorLoop.StartMonitorLoop(); +#endregion + +await host.WaitForShutdownAsync(); diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/README.md b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/README.md new file mode 100644 index 000000000000..64fb98f080f4 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/README.md @@ -0,0 +1,9 @@ +# ASP.NET Core Background Tasks Sample + +This sample illustrates the use of [IHostedService](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihostedservice). This sample demonstrates the features described in the [Background tasks with hosted services in ASP.NET Core](https://learn.microsoft.com/aspnet/core/fundamentals/host/hosted-services) topic. + +Run the sample from a command shell: + +``` +dotnet run +``` diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/BackgroundTaskQueue.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/BackgroundTaskQueue.cs new file mode 100644 index 000000000000..0eb1621987ec --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/BackgroundTaskQueue.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace BackgroundTasksSample.Services +{ + #region snippet1 + public interface IBackgroundTaskQueue + { + ValueTask QueueBackgroundWorkItemAsync(Func workItem); + + ValueTask> DequeueAsync( + CancellationToken cancellationToken); + } + + public class BackgroundTaskQueue : IBackgroundTaskQueue + { + private readonly Channel> _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>(options); + } + + public async ValueTask QueueBackgroundWorkItemAsync( + Func workItem) + { + if (workItem == null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + await _queue.Writer.WriteAsync(workItem); + } + + public async ValueTask> DequeueAsync( + CancellationToken cancellationToken) + { + var workItem = await _queue.Reader.ReadAsync(cancellationToken); + + return workItem; + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ConsumeScopedServiceHostedService.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ConsumeScopedServiceHostedService.cs new file mode 100644 index 000000000000..7615e4333cba --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ConsumeScopedServiceHostedService.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BackgroundTasksSample.Services +{ + #region snippet1 + public class ConsumeScopedServiceHostedService : BackgroundService + { + private readonly ILogger _logger; + + public ConsumeScopedServiceHostedService(IServiceProvider services, + ILogger 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(); + + await scopedProcessingService.DoWork(stoppingToken); + } + } + + public override async Task StopAsync(CancellationToken stoppingToken) + { + _logger.LogInformation( + "Consume Scoped Service Hosted Service is stopping."); + + await base.StopAsync(stoppingToken); + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/MonitorLoop.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/MonitorLoop.cs new file mode 100644 index 000000000000..b60af6d2b763 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/MonitorLoop.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BackgroundTasksSample.Services +{ + #region snippet_Monitor + public class MonitorLoop + { + private readonly IBackgroundTaskQueue _taskQueue; + private readonly ILogger _logger; + private readonly CancellationToken _cancellationToken; + + public MonitorLoop(IBackgroundTaskQueue taskQueue, + ILogger 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); + } + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/QueuedHostedService.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/QueuedHostedService.cs new file mode 100644 index 000000000000..c16931a0c674 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/QueuedHostedService.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BackgroundTasksSample.Services +{ + #region snippet1 + public class QueuedHostedService : BackgroundService + { + private readonly ILogger _logger; + + public QueuedHostedService(IBackgroundTaskQueue taskQueue, + ILogger 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); + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ScopedProcessingService.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ScopedProcessingService.cs new file mode 100644 index 000000000000..b164f4339ef8 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/ScopedProcessingService.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace BackgroundTasksSample.Services +{ + #region snippet1 + internal interface IScopedProcessingService + { + Task DoWork(CancellationToken stoppingToken); + } + + internal class ScopedProcessingService : IScopedProcessingService + { + private int executionCount = 0; + private readonly ILogger _logger; + + public ScopedProcessingService(ILogger 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); + } + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/TimedHostedService.cs b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/TimedHostedService.cs new file mode 100644 index 000000000000..4e324a767dc9 --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/Services/TimedHostedService.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BackgroundTasksSample.Services +{ + #region snippet1 + public class TimedHostedService : IHostedService, IDisposable + { + private int executionCount = 0; + private readonly ILogger _logger; + private Timer? _timer = null; + + public TimedHostedService(ILogger 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(); + } + } + #endregion +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.Development.json b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.Development.json new file mode 100644 index 000000000000..8983e0fc1c5e --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.json b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.json new file mode 100644 index 000000000000..8983e0fc1c5e --- /dev/null +++ b/aspnetcore/fundamentals/host/hosted-services/samples/8.0/BackgroundTasksSample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +}