Run / RunEither
Executes the train from the outside. Run throws on failure; RunEither returns an Either<Exception, TReturn> for Railway-oriented error handling.
These are called by consumers of the train, not inside Junctions().
Signatures
Run (throws on failure)
public virtual async Task<TReturn> Run(TInput input, CancellationToken cancellationToken = default)On a ServiceTrain it is a sealed override, and the two overloads that take a pre-created
Metadata are not virtual:
public sealed override Task<TOut> Run(TIn input, CancellationToken cancellationToken = default)
public Task<TOut> Run(TIn input, Metadata metadata)
public Task<TOut> Run(TIn input, Metadata metadata, CancellationToken cancellationToken)A service train does its work in Junctions(), and Run owns the metadata row, the lifecycle
hooks and the outcome write around it, so none of them can be overridden. A plain
Train<TInput, TReturn> keeps a virtual Run.
RunEither (returns Either)
public Task<Either<Exception, TReturn>> RunEither(TInput input)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
input | TInput | Yes | The input data for the train |
cancellationToken | CancellationToken | No | Token to monitor for cancellation requests. When provided, it is stored on the Train.CancellationToken property and propagated to every junction before execution. Defaults to CancellationToken.None when omitted. |
Note:
RunEitherdoes not accept aCancellationTokenparameter, and a caller cannot assignTrain.CancellationToken(its setter is not public). Pass the token toRun, or run the train through a host (aServiceTrainresolved from the container, the mediator or the scheduler), which sets it.
Returns
Run:Task<TReturn>, the train result. Throws the captured exception if the train failed. ThrowsOperationCanceledExceptionif the token is cancelled.RunEither:Task<Either<Exception, TReturn>>,Lefton failure,Righton success. Nothing escapes it, cancellation included: a cancelled run returnsLeftholding theOperationCanceledException. Tell it apart from a business failure withis OperationCanceledExceptionbefore treating theLeftas one.
Examples
Using Run (imperative style)
try
{
var result = await train.Run(new OrderInput { OrderId = "123" });
Console.WriteLine($"Order processed: {result.ConfirmationId}");
}
catch (Exception ex)
{
Console.WriteLine($"Train failed: {ex.Message}");
}Using RunEither (functional style)
var result = await train.RunEither(new OrderInput { OrderId = "123" });
result.Match(
Right: success => Console.WriteLine($"Order processed: {success.ConfirmationId}"),
Left: error => Console.WriteLine($"Train failed: {error.Message}")
);With CancellationToken
// From an ASP.NET controller
public async Task<IActionResult> ProcessOrder(
OrderInput input,
CancellationToken cancellationToken)
{
var result = await train.Run(input, cancellationToken);
return Ok(result);
}Behavior
- If a
CancellationTokenis provided, stores it on theTrain.CancellationTokenproperty. - Seeds
Memorywith the input. - Calls the train's
Junctions()declaration. Run: Unwraps theEitherresult. IfLeft, rethrows the exception. IfRight, returns the value.RunEither: Returns theEitherdirectly without unwrapping.
During junction execution, the train's CancellationToken is automatically propagated to each junction before its Run method is called. Junctions access the token via this.CancellationToken. Before each junction executes, CancellationToken.ThrowIfCancellationRequested() is called. If the token is already cancelled, the junction is skipped entirely.
Remarks
RunEitheris useful when you want functional-style error handling without try/catch. It pairs naturally with LanguageExt'sMatch,Map,Bind, etc.- In most applications, trains are executed through
ITrainBus.RunAsync(which callsRuninternally) rather than callingRundirectly. See TrainBus. - The
cancellationTokenparameter stores the token before calling the train's route definition. All junctions in the chain then receive the token automatically. See Cancellation Tokens for details on how cancellation propagates through the pipeline. Runisvirtualon plainTrain<TInput, TReturn>, for code that uses Trax.Core without Trax.Effect. An override that does not callbase.Runnever reachesJunctions(), so the chain thatDeclaredChain()reads, and any check built on it, is not the chain that runs. Callbase.Runfrom an override, or put the work in a junction. AServiceTraincannot overrideRunat all: its overloads are sealed.Trax.Core/docs/adr/0003records why.