Skip to content

Commit 33f1459

Browse files
[Orleans] Update startup tasks guidance (#43713)
* [Orleans] Update startup tasks guidance * Update docs/orleans/host/configuration-guide/startup-tasks.md * Apply suggestions from code review --------- Co-authored-by: David Pine <[email protected]>
1 parent 66c79cc commit 33f1459

File tree

1 file changed

+140
-26
lines changed

1 file changed

+140
-26
lines changed
Lines changed: 140 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,158 @@
11
---
2-
title: Startup tasks
3-
description: Learn how to configure and manage startup tasks in .NET Orleans.
4-
ms.date: 07/03/2024
2+
title: Background Services and Startup Tasks
3+
description: Learn how to configure and manage background services and startup tasks in .NET Orleans.
4+
ms.date: 11/19/2024
55
---
66

7-
# Startup tasks
7+
# Background Services and Startup Tasks
88

9-
In many cases, some task needs to be performed automatically as soon as a silo becomes available. Startup tasks provide this functionality.
9+
When building Orleans applications, you often need to perform background operations or initialize components when the application starts.
1010

11-
Some use cases include, but are not limited to:
11+
Startup tasks can be used to perform initialization work when a silo starts, before or after it begins accepting requests. Common use cases include:
1212

13-
* Starting background timers to perform periodic housekeeping tasks
14-
* Pre-loading some cache grains with data downloaded from external backing storage
13+
* Initializing grain state or preloading data
14+
* Setting up external service connections
15+
* Performing database migrations
16+
* Validating configuration
17+
* Warming up caches
1518

16-
Any exceptions that are thrown from a startup task during startup will be reported in the silo log and will stop the silo.
19+
## Using BackgroundService (Recommended)
1720

18-
This fail-fast approach is the standard way that Orleans handles silo start-up issues, and is intended to allow any problems with silo configuration and/or bootstrap logic to be easily detected during testing phases rather than being silently ignored and causing unexpected problems later in the silo lifecycle.
21+
The recommended approach is to use .NET [BackgroundService or `IHostedService`](/aspnet/core/fundamentals/host/hosted-services). See the [Background tasks with hosted services in ASP.NET Core](/aspnet/core/fundamentals/host/hosted-services) documentation for more information.
1922

20-
## Configure startup tasks
23+
Here's an example that pings a grain every 5 seconds:
2124

22-
Startup tasks can be configured using the <xref:Orleans.Hosting.ISiloHostBuilder> either by registering a delegate to be invoked during startup or by registering an implementation of <xref:Orleans.Runtime.IStartupTask>.
25+
```csharp
26+
public class GrainPingService : BackgroundService
27+
{
28+
private readonly IGrainFactory _grainFactory;
29+
private readonly ILogger<GrainPingService> _logger;
2330

24-
### Register a delegate
31+
public GrainPingService(
32+
IGrainFactory grainFactory,
33+
ILogger<GrainPingService> logger)
34+
{
35+
_grainFactory = grainFactory;
36+
_logger = logger;
37+
}
38+
39+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
40+
{
41+
try
42+
{
43+
while (!stoppingToken.IsCancellationRequested)
44+
{
45+
try
46+
{
47+
_logger.LogInformation("Pinging grain...");
48+
var grain = _grainFactory.GetGrain<IMyGrain>("ping-target");
49+
await grain.Ping();
50+
}
51+
catch (Exception ex) when (ex is not OperationCanceledException)
52+
{
53+
// Log the error but continue running
54+
_logger.LogError(ex, "Failed to ping grain. Will retry in 5 seconds.");
55+
}
56+
57+
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
58+
}
59+
}
60+
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
61+
{
62+
// Ignore cancellation during shutdown.
63+
}
64+
finally
65+
{
66+
_logger.LogInformation("Grain ping service is shutting down.");
67+
}
68+
}
69+
}
70+
```
71+
72+
Registration order is significant, since services added to the host builder are started one-by-one, in the order they are registered. You can register the background service as follows:
2573

2674
```csharp
27-
siloHostBuilder.AddStartupTask(
28-
async (IServiceProvider services, CancellationToken cancellation) =>
29-
{
30-
// Use the service provider to get the grain factory.
31-
var grainFactory = services.GetRequiredService<IGrainFactory>();
32-
33-
// Get a reference to a grain and call a method on it.
34-
var grain = grainFactory.GetGrain<IMyGrain>(0);
35-
await grain.Initialize();
75+
var builder = WebApplication.CreateBuilder(args);
76+
77+
// Configure Orleans first
78+
builder.UseOrleans(siloBuilder =>
79+
{
80+
// Orleans configuration...
3681
});
82+
83+
// Register the background service after calling 'UseOrleans' to make it start once Orleans has started.
84+
builder.Services.AddHostedService<GrainPingService>();
85+
86+
var app = builder.Build();
87+
```
88+
89+
The background service will start automatically when the application starts and will gracefully shut down when the application stops.
90+
91+
## Using IHostedService
92+
93+
For simpler scenarios where you don't need continuous background operation, you can implement `IHostedService` directly:
94+
95+
```csharp
96+
public class GrainInitializerService : IHostedService
97+
{
98+
private readonly IGrainFactory _grainFactory;
99+
private readonly ILogger<GrainInitializerService> _logger;
100+
101+
public GrainInitializerService(
102+
IGrainFactory grainFactory,
103+
ILogger<GrainInitializerService> logger)
104+
{
105+
_grainFactory = grainFactory;
106+
_logger = logger;
107+
}
108+
109+
public async Task StartAsync(CancellationToken cancellationToken)
110+
{
111+
_logger.LogInformation("Initializing grains...");
112+
var grain = _grainFactory.GetGrain<IMyGrain>("initializer");
113+
await grain.Initialize();
114+
}
115+
116+
public Task StopAsync(CancellationToken cancellationToken)
117+
{
118+
return Task.CompletedTask;
119+
}
120+
}
121+
```
122+
123+
Register it the same way:
124+
125+
```csharp
126+
builder.Services.AddHostedService<GrainInitializerService>();
127+
```
128+
129+
## Orleans' Startup Tasks
130+
131+
> [!NOTE]
132+
> While startup tasks are still supported, we recommend using `BackgroundService` or `IHostedService` instead as they are the common .NET hosting mechanism for running background tasks.
133+
134+
> [!WARNING]
135+
> Any exceptions thrown from a startup task will be reported in the silo log and will stop the silo. This fail-fast approach helps detect configuration and bootstrap issues during testing rather than having them cause unexpected problems later, but it can also mean that transient failures in a startup task will cause unavailability of the host.
136+
137+
If you need to use the built-in startup task system, you can configure them as follows:
138+
139+
### Register a delegate
140+
141+
A delegate can be registered as a startup task using the appropriate <xref:Orleans.Hosting.SiloBuilderStartupExtensions.AddStartupTask*> extension method on <xref:Orleans.Hosting.ISiloBuilder>.
142+
143+
```csharp
144+
siloBuilder.AddStartupTask(
145+
async (IServiceProvider services, CancellationToken cancellation) =>
146+
{
147+
var grainFactory = services.GetRequiredService<IGrainFactory>();
148+
var grain = grainFactory.GetGrain<IMyGrain>("startup-task-grain");
149+
await grain.Initialize();
150+
});
37151
```
38152

39153
### Register an `IStartupTask` implementation
40154

41-
First, we must define an implementation of `IStartupTask`:
155+
The <xref:Orleans.Runtime.IStartupTask> interface can be implemented and registered as a startup task using the <xref:Orleans.Hosting.SiloBuilderStartupExtensions.AddStartupTask*> extension method on <xref:Orleans.Hosting.ISiloBuilder>.
42156

43157
```csharp
44158
public class CallGrainStartupTask : IStartupTask
@@ -50,14 +164,14 @@ public class CallGrainStartupTask : IStartupTask
50164

51165
public async Task Execute(CancellationToken cancellationToken)
52166
{
53-
var grain = _grainFactory.GetGrain<IMyGrain>(0);
167+
var grain = _grainFactory.GetGrain<IMyGrain>("startup-task-grain");
54168
await grain.Initialize();
55169
}
56170
}
57171
```
58172

59-
Then that implementation must be registered with the `ISiloHostBuilder`:
173+
Register the startup task as follows:
60174

61175
```csharp
62-
siloHostBuilder.AddStartupTask<CallGrainStartupTask>();
176+
siloBuilder.AddStartupTask<CallGrainStartupTask>();
63177
```

0 commit comments

Comments
 (0)