Develop Orleans grains
A grain is an application object with a stable logical identity. Orleans activates it on demand, routes calls to its current activation, and removes idle activations from memory. Application code works with grain references instead of constructing grain classes or locating activations.
Projects defining grain contracts or implementations reference Microsoft.Orleans.Sdk. For host and project setup, see Build your first Orleans app.
Define a grain contract
Section titled “Define a grain contract”A grain interface derives from one of the grain key interfaces and declares asynchronous methods:
public interface IShoppingCartGrain : IGrainWithStringKey{ ValueTask AddItem(CartItem item);
ValueTask<IReadOnlyList<CartItem>> GetItems();
Task Checkout();
Task<Receipt> GetReceipt();}
[GenerateSerializer]public sealed record CartItem( [Id(0)] string ProductId, [Id(1)] int Quantity);
[GenerateSerializer]public sealed record Receipt( [Id(0)] string OrderId);Orleans supports these grain method return types:
Use Task or ValueTask for methods without a result, and their generic forms for methods returning a result. Use IAsyncEnumerable<T> for response streaming. Don’t use void, async void, or synchronous return types in grain contracts. A CancellationToken can be included as a method parameter for cooperative cancellation. For the underlying C# model, see Asynchronous programming.
Arguments, return values, and exceptions cross process boundaries. Make application data serializable by Orleans, normally using GenerateSerializerAttribute and stable IdAttribute values. Grain references are already serializable and can be passed in calls or stored as part of grain state.
Implement the contract
Section titled “Implement the contract”A grain class usually derives from Grain and implements one or more grain interfaces:
public sealed class ShoppingCartGrain : Grain, IShoppingCartGrain{ private readonly List<CartItem> _items = [];
public ValueTask AddItem(CartItem item) { _items.Add(item); return ValueTask.CompletedTask; }
public ValueTask<IReadOnlyList<CartItem>> GetItems() => ValueTask.FromResult<IReadOnlyList<CartItem>>(_items.ToArray());
public Task Checkout() => Task.CompletedTask;
public Task<Receipt> GetReceipt() => Task.FromResult(new Receipt($"order-{this.GetPrimaryKeyString()}"));}Orleans creates grain classes through dependency injection. Constructor injection is available for application services, and IGrainFactory is available through Grain.GrainFactory.
Get and call a grain reference
Section titled “Get and call a grain reference”Use GetGrain with the interface and key:
IShoppingCartGrain cart = grainFactory.GetGrain<IShoppingCartGrain>("customer-42");
await cart.AddItem(new CartItem("SKU-123", 2));IReadOnlyList<CartItem> items = await cart.GetItems();Getting a reference doesn’t create or activate a grain. The first call that needs an activation causes Orleans to place and activate it. The reference remains valid if the activation moves, deactivates, or is recreated on another silo.
Understand call completion
Section titled “Understand call completion”A regular grain call completes in one of these ways:
- The method returns successfully, optionally with a result.
- The method throws, and the exception is propagated to the caller.
- The caller cancels the call and observes OperationCanceledException.
- The caller doesn’t receive a response before its response timeout and observes TimeoutException.
- Messaging or cluster failures prevent the call from completing.
A timeout tells the caller that no response arrived in time. It doesn’t prove that the grain method didn’t run or won’t finish. CancelRequestOnTimeout defaults to false; when enabled, Orleans sends a best-effort cancellation signal after a timeout, and the grain must still cooperate by observing a cancellation token.
Distributed calls can be retried by application code or infrastructure after an uncertain outcome. Design operations to be idempotent when duplicate execution would be harmful. A common pattern is to include an operation ID and persist completed IDs with the state change.
Configure a per-method timeout on the interface:
public interface IReportGrain : IGrainWithGuidKey{ [ResponseTimeout("00:00:10")] Task<Report> Generate(CancellationToken cancellationToken = default);}Global defaults are configured through ClientMessagingOptions and SiloMessagingOptions. See client configuration, server configuration, and cancellation tokens.
Activation and deactivation
Section titled “Activation and deactivation”Override the current lifecycle methods when a grain needs activation-scoped setup or cleanup:
public override Task OnActivateAsync(CancellationToken cancellationToken){ return base.OnActivateAsync(cancellationToken);}
public override Task OnDeactivateAsync( DeactivationReason reason, CancellationToken cancellationToken){ return base.OnDeactivateAsync(reason, cancellationToken);}OnActivateAsync accepts a CancellationToken; there is no parameterless overload. Deactivation callbacks are best effort and don’t run after process termination or some failures, so don’t rely on them to persist critical state.
See Grain lifecycle for collection, lifecycle participation, and migration.
Choose basic or advanced features
Section titled “Choose basic or advanced features”Most grains only need a contract, an implementation, a stable key, and regular request-response calls. Add specialized behavior only when the workload requires it:
- Request scheduling and reentrancy
- Response streaming with IAsyncEnumerable
- Timers and reminders
- Observers
- Grain placement
- Stateless worker grains
- Grain call filters
- Grain extensions
- Grain services
The Orleans runtime implementation documentation describes internal scheduling, messaging, and lifecycle components. Those details aren’t required for basic grain development.
