/// <summary> /// Executes child nodes of the current node. /// </summary> /// <param name="context">Current ExecutionContext.</param> /// <returns>NodeResultStatus representing the current node result.</returns> protected sealed override async Task <NodeResultStatus> PerformExecuteAsync(IExecutionContext <TSource> context) { if (ChildNode == null) { Logger.LogWarning("Child node of TransitionNode doesn't exist, node will be skipped."); return(NodeResultStatus.NotRun); } Logger.LogDebug("Creating the TransitionNode destination subject."); TDestination destSubject = await TransitionSourceAsync(context).ConfigureAwait(false); var destContext = new ExecutionContext <TDestination>(destSubject, context.GlobalOptions); Logger.LogDebug("Preparing to execute TransitionNode child."); NodeResult destResult = await ChildNode.ExecuteAsync(destContext).ConfigureAwait(false); var exceptions = destResult.GetFailExceptions().ToList(); if (exceptions.Count > 0) { Logger.LogInformation("TransitionNode child returned {0} exceptions.", exceptions.Count); context.ParentResult.Exception = exceptions.Count == 1 ? exceptions[0] : new AggregateException(exceptions); } Logger.LogDebug("Creating the TransitionNode destination result."); var resultSubject = await TransitionResultAsync(context, destResult).ConfigureAwait(false); if (!context.Subject.Equals(resultSubject)) { Logger.LogDebug("Source subject has changed, calling ChangeSubject."); context.ChangeSubject(resultSubject); } return(destResult.Status); }
/// <summary> /// Used to kick off execution of a node with a default execution context for all subjects using Async WhenAll semantics internally . /// </summary> /// <param name="subjects">Subject to be moved through the node.</param> /// <param name="options">Execution options to apply to running this enumerable of subjects.</param> /// <returns>An aggregated NodeResult.</returns> public async Task <NodeResult> ExecuteManyAsync(IEnumerable <T> subjects, ExecutionOptions options = null) { Guard.AgainstNullArgument("subjects", subjects); var nodeTimer = new NodeTimer(); try { nodeTimer.LogStart(LogWriter, this, "ExecuteManyAsync"); _processManyMode = true; Result = new NodeResult(default(T), Id, FlowId); var subjectList = subjects.ToList(); if (subjectList.Count == 0) { return(Result); } if (options == null) { options = new ExecutionOptions(); } LogWriter.Debug("Running all subjects asynchronously."); Task aggregateTask = null; try { var resultsQueue = new ConcurrentQueue <NodeResult>(); aggregateTask = subjectList.ForEachAsync(options.DegreeOfParallelism, async x => resultsQueue.Enqueue(await ExecuteAsync(new ExecutionContext <T>(x, options)))); await aggregateTask; Result.AddChildResults(resultsQueue); } catch { if (options.ThrowOnError) { if (aggregateTask != null && aggregateTask.Exception != null) { throw aggregateTask.Exception; } throw; } } ProcessExecuteManyResults(options); return(Result); } finally { _processManyMode = false; nodeTimer.LogStop(LogWriter, this, "ExecuteAsync"); } }
/// <summary> /// Used to kick off execution of a node with a default execution context for all subjects in a serial manner. /// </summary> /// <param name="subjects">Subject to be moved through the node.</param> /// <param name="options">Execution options to apply to running this enumerable of subjects.</param> /// <returns>An aggregated NodeResult.</returns> public async Task <NodeResult> ExecuteManySeriallyAsync(IEnumerable <T> subjects, ExecutionOptions options = null) { Guard.AgainstNullArgument("subjects", subjects); var nodeTimer = new NodeTimer(); try { nodeTimer.LogStart(LogWriter, this, "ExecuteManySeriallyAsync"); _processManyMode = true; var subjectList = subjects.ToList(); Result = new NodeResult(default(T), Id, FlowId); if (subjectList.Count == 0) { return(Result); } if (options == null) { options = new ExecutionOptions(); } foreach (var subject in subjectList) { try { LogWriter.Debug("Running all subjects asynchronously in a serial manner."); NodeResult result = await ExecuteAsync(new ExecutionContext <T>(subject, options)).ConfigureAwait(false); Result.AddChildResult(result); } catch (Exception) { if (options.ThrowOnError) { throw; } if (!options.ContinueOnFailure) { break; } } } ProcessExecuteManyResults(options); return(Result); } finally { _processManyMode = false; nodeTimer.LogStop(LogWriter, this, "ExecuteAsync"); } }
/// <summary> /// Creates a new execution context. /// </summary> /// <param name="subject">Subject of the current flow.</param> /// <param name="globalOptions">Global options of the current flow.</param> /// <param name="rootResult">Root result if one has already been established.</param> public ExecutionContext(T subject, ExecutionOptions globalOptions = null, NodeResult rootResult = null) { State = new DynamicDictionary(); Subject = subject; GlobalOptions = globalOptions ?? new ExecutionOptions(); if (rootResult != null) { ParentResult = rootResult; } }
/// <summary> /// Prepares the execution context before the current node is run. /// </summary> /// <param name="context">Source context for preparation.</param> /// <param name="result">The result reference to add to the current context.</param> /// <returns>The execution context to be used in node execution.</returns> protected sealed override IExecutionContext <T> PrepareExecutionContext(IExecutionContext <T> context, NodeResult result) { Logger.LogDebug("Preparing execution context."); var resultContext = new ExecutionContext <T>(context, result); context.AddResult(result); return(resultContext); }
/// <summary> /// Adds a result to the execution context. /// </summary> /// <param name="result">The result to add</param> public void AddResult(NodeResult result) { if (ParentResult == null) { ParentResult = result; } else if (ParentResult != result) { ParentResult.AddChildResult(result); } }
/// <summary> /// Creates a child context based on the parent context. Used for nesting multi-nodes inside of other nodes. /// </summary> /// <param name="parentContext">Parent of this context.</param> /// <param name="parentResult">Parent result to set on the new context, if any.</param> internal ExecutionContext(IExecutionContext <T> parentContext, NodeResult parentResult = null) { Guard.AgainstNullArgument("parentContext", parentContext); Guard.AgainstNullArgumentProperty("parentContext", "Subject", parentContext.Subject); Guard.AgainstNullArgumentProperty("parentContext", "GlobalOptions", parentContext.GlobalOptions); Subject = parentContext.Subject; State = parentContext.State; GlobalOptions = parentContext.GlobalOptions; ParentResult = parentResult; CancelProcessing = parentContext.CancelProcessing; }
/// <summary> /// Executes child nodes of the current node. /// </summary> /// <param name="context">Current ExecutionContext.</param> /// <returns>NodeResultStatus representing the current node result.</returns> protected override async Task <NodeResultStatus> ExecuteChildrenAsync(IExecutionContext <T> context) { NodeResult result = null; Logger.LogDebug("Running first matching child node."); foreach (var childNode in Children) { result = await childNode.ExecuteAsync(context).ConfigureAwait(false); if (result.Status != NodeResultStatus.NotRun || context.CancelProcessing) { break; } } if (result != null) { return(result.Status); } return(NodeResultStatus.NotRun); }
/// <summary> /// Adds a child result to the current result. /// </summary> /// <param name="result">Result to add to child results.</param> internal void AddChildResult(NodeResult result) { _childResults.Enqueue(result); }
/// <summary> /// Transitions the source based on the child result to prepare for return to the source flow. /// </summary> /// <param name="sourceContext">Context including the source subject.</param> /// <param name="result">The result of the destination node.</param> /// <returns>The transitioned subject.</returns> protected async override sealed Task <TSource> TransitionResultAsync(IExecutionContext <TSource> sourceContext, NodeResult result) { if (TransitionResultFuncAsync != null) { LogWriter.Debug("TransitionResultFuncAsync exists, using this function."); return(await TransitionResultFuncAsync(sourceContext, result).ConfigureAwait(false)); } LogWriter.Debug("TransitionResultFuncAsync doesn't exist, returning original subject."); return(sourceContext.Subject); }
/// <summary> /// Transitions the source based on the child result to prepare for. /// </summary> /// <param name="sourceContext">Context including the source subject.</param> /// <param name="result">The result of the destination node.</param> /// <returns>The transitioned subject.</returns> protected virtual Task <TSource> TransitionResultAsync(IExecutionContext <TSource> sourceContext, NodeResult result) { return(Task.FromResult(sourceContext.Subject)); }
/// <summary> /// Prepares the execution context before the current node is run. /// </summary> /// <param name="context">Source context for preparation.</param> /// <param name="result">The result reference to add to the current context.</param> /// <returns>The execution context to be used in node execution.</returns> protected virtual IExecutionContext <T> PrepareExecutionContext(IExecutionContext <T> context, NodeResult result) { LogWriter.Debug("Preparing the execution context for execution."); context.AddResult(result); return(context); }
/// <summary> /// Used to kick off execution of a node with a specified execution context. /// </summary> /// <param name="sourceContext">ExecutionContext that includes a subject to be moved through the node.</param> /// <returns>A NodeResult</returns> public async Task <NodeResult> ExecuteAsync(IExecutionContext <T> sourceContext) { Guard.AgainstNullArgument("context", sourceContext); Guard.AgainstNullArgumentProperty("context", "Subject", sourceContext.Subject); var nodeTimer = new NodeTimer(); try { nodeTimer.LogStart(LogWriter, this, "ExecuteAsync"); if (Status != NodeRunStatus.NotRun) { LogWriter.Debug("Status does not equal 'NotRun', resetting the node before execution"); Reset(); } var subject = sourceContext.Subject; var result = new NodeResult(subject, Id, FlowId); if (!_processManyMode) { Result = result; } IExecutionContext <T> context = PrepareExecutionContext(sourceContext, result); OnBeforeExecute(context); if (!context.CancelProcessing) { var effectiveOptions = GetEffectiveOptions(context.GlobalOptions); if (!await ShouldExecuteInternalAsync(context).ConfigureAwait(false)) { LogWriter.Info("ShouldExecute returned false, skipping execution"); return(result); } Status = NodeRunStatus.Running; LogWriter.Debug("Executing the node"); try { result.Status = await PerformExecuteAsync(context).ConfigureAwait(false); //Reset the subject in case it was changed. result.Subject = context.Subject; Status = NodeRunStatus.Completed; LogWriter.Info("Node completed execution, status is {0}", result.Status); } catch (Exception ex) { LogWriter.Error("Node erred during execution, status is Failed", ex); Status = NodeRunStatus.Faulted; result.Subject = context.Subject; result.Status = NodeResultStatus.Failed; result.Exception = ex; if (effectiveOptions.ThrowOnError) { throw; } } OnAfterExecute(context); } sourceContext.CancelProcessing = context.CancelProcessing; return(result); } finally { nodeTimer.LogStop(LogWriter, this, "ExecuteAsync"); } }