Skip to content

Commit 9d8cdf7

Browse files
Update Hosted Service Sample to .net8.0 (#32991)
1 parent 6474dea commit 9d8cdf7

File tree

11 files changed

+424
-0
lines changed

11 files changed

+424
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Worker">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<RootNamespace>BackgroundTasksSample</RootNamespace>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using BackgroundTasksSample.Services;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Hosting;
4+
5+
using var host = Host.CreateDefaultBuilder(args)
6+
.ConfigureServices((hostContext, services) =>
7+
{
8+
#region snippet3
9+
services.AddSingleton<MonitorLoop>();
10+
services.AddHostedService<QueuedHostedService>();
11+
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
12+
{
13+
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
14+
queueCapacity = 100;
15+
return new BackgroundTaskQueue(queueCapacity);
16+
});
17+
#endregion
18+
19+
#region snippet1
20+
services.AddHostedService<TimedHostedService>();
21+
#endregion
22+
23+
#region snippet2
24+
services.AddHostedService<ConsumeScopedServiceHostedService>();
25+
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
26+
#endregion
27+
})
28+
.Build();
29+
30+
await host.StartAsync();
31+
32+
#region snippet4
33+
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
34+
monitorLoop.StartMonitorLoop();
35+
#endregion
36+
37+
await host.WaitForShutdownAsync();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ASP.NET Core Background Tasks Sample
2+
3+
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.
4+
5+
Run the sample from a command shell:
6+
7+
```
8+
dotnet run
9+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Channels;
4+
using System.Threading.Tasks;
5+
6+
namespace BackgroundTasksSample.Services
7+
{
8+
#region snippet1
9+
public interface IBackgroundTaskQueue
10+
{
11+
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
12+
13+
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
14+
CancellationToken cancellationToken);
15+
}
16+
17+
public class BackgroundTaskQueue : IBackgroundTaskQueue
18+
{
19+
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
20+
21+
public BackgroundTaskQueue(int capacity)
22+
{
23+
// Capacity should be set based on the expected application load and
24+
// number of concurrent threads accessing the queue.
25+
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
26+
// which completes only when space became available. This leads to backpressure,
27+
// in case too many publishers/calls start accumulating.
28+
var options = new BoundedChannelOptions(capacity)
29+
{
30+
FullMode = BoundedChannelFullMode.Wait
31+
};
32+
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
33+
}
34+
35+
public async ValueTask QueueBackgroundWorkItemAsync(
36+
Func<CancellationToken, ValueTask> workItem)
37+
{
38+
if (workItem == null)
39+
{
40+
throw new ArgumentNullException(nameof(workItem));
41+
}
42+
43+
await _queue.Writer.WriteAsync(workItem);
44+
}
45+
46+
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
47+
CancellationToken cancellationToken)
48+
{
49+
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
50+
51+
return workItem;
52+
}
53+
}
54+
#endregion
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Hosting;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace BackgroundTasksSample.Services
9+
{
10+
#region snippet1
11+
public class ConsumeScopedServiceHostedService : BackgroundService
12+
{
13+
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
14+
15+
public ConsumeScopedServiceHostedService(IServiceProvider services,
16+
ILogger<ConsumeScopedServiceHostedService> logger)
17+
{
18+
Services = services;
19+
_logger = logger;
20+
}
21+
22+
public IServiceProvider Services { get; }
23+
24+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
25+
{
26+
_logger.LogInformation(
27+
"Consume Scoped Service Hosted Service running.");
28+
29+
await DoWork(stoppingToken);
30+
}
31+
32+
private async Task DoWork(CancellationToken stoppingToken)
33+
{
34+
_logger.LogInformation(
35+
"Consume Scoped Service Hosted Service is working.");
36+
37+
using (var scope = Services.CreateScope())
38+
{
39+
var scopedProcessingService =
40+
scope.ServiceProvider
41+
.GetRequiredService<IScopedProcessingService>();
42+
43+
await scopedProcessingService.DoWork(stoppingToken);
44+
}
45+
}
46+
47+
public override async Task StopAsync(CancellationToken stoppingToken)
48+
{
49+
_logger.LogInformation(
50+
"Consume Scoped Service Hosted Service is stopping.");
51+
52+
await base.StopAsync(stoppingToken);
53+
}
54+
}
55+
#endregion
56+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace BackgroundTasksSample.Services
8+
{
9+
#region snippet_Monitor
10+
public class MonitorLoop
11+
{
12+
private readonly IBackgroundTaskQueue _taskQueue;
13+
private readonly ILogger _logger;
14+
private readonly CancellationToken _cancellationToken;
15+
16+
public MonitorLoop(IBackgroundTaskQueue taskQueue,
17+
ILogger<MonitorLoop> logger,
18+
IHostApplicationLifetime applicationLifetime)
19+
{
20+
_taskQueue = taskQueue;
21+
_logger = logger;
22+
_cancellationToken = applicationLifetime.ApplicationStopping;
23+
}
24+
25+
public void StartMonitorLoop()
26+
{
27+
_logger.LogInformation("MonitorAsync Loop is starting.");
28+
29+
// Run a console user input loop in a background thread
30+
Task.Run(async () => await MonitorAsync());
31+
}
32+
33+
private async ValueTask MonitorAsync()
34+
{
35+
while (!_cancellationToken.IsCancellationRequested)
36+
{
37+
var keyStroke = Console.ReadKey();
38+
39+
if (keyStroke.Key == ConsoleKey.W)
40+
{
41+
// Enqueue a background work item
42+
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
43+
}
44+
}
45+
}
46+
47+
private async ValueTask BuildWorkItem(CancellationToken token)
48+
{
49+
// Simulate three 5-second tasks to complete
50+
// for each enqueued work item
51+
52+
int delayLoop = 0;
53+
var guid = Guid.NewGuid().ToString();
54+
55+
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
56+
57+
while (!token.IsCancellationRequested && delayLoop < 3)
58+
{
59+
try
60+
{
61+
await Task.Delay(TimeSpan.FromSeconds(5), token);
62+
}
63+
catch (OperationCanceledException)
64+
{
65+
// Prevent throwing if the Delay is cancelled
66+
}
67+
68+
delayLoop++;
69+
70+
_logger.LogInformation("Queued Background Task {Guid} is running. "
71+
+ "{DelayLoop}/3", guid, delayLoop);
72+
}
73+
74+
if (delayLoop == 3)
75+
{
76+
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
77+
}
78+
else
79+
{
80+
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
81+
}
82+
}
83+
}
84+
#endregion
85+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace BackgroundTasksSample.Services
8+
{
9+
#region snippet1
10+
public class QueuedHostedService : BackgroundService
11+
{
12+
private readonly ILogger<QueuedHostedService> _logger;
13+
14+
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
15+
ILogger<QueuedHostedService> logger)
16+
{
17+
TaskQueue = taskQueue;
18+
_logger = logger;
19+
}
20+
21+
public IBackgroundTaskQueue TaskQueue { get; }
22+
23+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
24+
{
25+
_logger.LogInformation(
26+
$"Queued Hosted Service is running.{Environment.NewLine}" +
27+
$"{Environment.NewLine}Tap W to add a work item to the " +
28+
$"background queue.{Environment.NewLine}");
29+
30+
await BackgroundProcessing(stoppingToken);
31+
}
32+
33+
private async Task BackgroundProcessing(CancellationToken stoppingToken)
34+
{
35+
while (!stoppingToken.IsCancellationRequested)
36+
{
37+
var workItem =
38+
await TaskQueue.DequeueAsync(stoppingToken);
39+
40+
try
41+
{
42+
await workItem(stoppingToken);
43+
}
44+
catch (Exception ex)
45+
{
46+
_logger.LogError(ex,
47+
"Error occurred executing {WorkItem}.", nameof(workItem));
48+
}
49+
}
50+
}
51+
52+
public override async Task StopAsync(CancellationToken stoppingToken)
53+
{
54+
_logger.LogInformation("Queued Hosted Service is stopping.");
55+
56+
await base.StopAsync(stoppingToken);
57+
}
58+
}
59+
#endregion
60+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace BackgroundTasksSample.Services
6+
{
7+
#region snippet1
8+
internal interface IScopedProcessingService
9+
{
10+
Task DoWork(CancellationToken stoppingToken);
11+
}
12+
13+
internal class ScopedProcessingService : IScopedProcessingService
14+
{
15+
private int executionCount = 0;
16+
private readonly ILogger _logger;
17+
18+
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
19+
{
20+
_logger = logger;
21+
}
22+
23+
public async Task DoWork(CancellationToken stoppingToken)
24+
{
25+
while (!stoppingToken.IsCancellationRequested)
26+
{
27+
executionCount++;
28+
29+
_logger.LogInformation(
30+
"Scoped Processing Service is working. Count: {Count}", executionCount);
31+
32+
await Task.Delay(10000, stoppingToken);
33+
}
34+
}
35+
}
36+
#endregion
37+
}

0 commit comments

Comments
 (0)