diff --git a/src/RulesEngine/Models/ReSettings.cs b/src/RulesEngine/Models/ReSettings.cs
index a4a4262c..ef392277 100644
--- a/src/RulesEngine/Models/ReSettings.cs
+++ b/src/RulesEngine/Models/ReSettings.cs
@@ -30,6 +30,7 @@ internal ReSettings(ReSettings reSettings)
UseFastExpressionCompiler = reSettings.UseFastExpressionCompiler;
EnableExceptionAsErrorMessageForRuleExpressionParsing = reSettings.EnableExceptionAsErrorMessageForRuleExpressionParsing;
AutoExecuteActions = reSettings.AutoExecuteActions;
+ EnableParallelRuleCompilation = reSettings.EnableParallelRuleCompilation;
}
@@ -98,6 +99,13 @@ internal ReSettings(ReSettings reSettings)
/// run actions yourself (e.g. via ExecuteActionWorkflowAsync) for selective control. See #596.
///
public bool AutoExecuteActions { get; set; } = true;
+
+ ///
+ /// When true, rules within a workflow are compiled in parallel during registration.
+ /// Significantly reduces warmup time for workflows with many rules.
+ /// Default: false
+ ///
+ public bool EnableParallelRuleCompilation { get; set; } = false;
}
public enum NestedRuleExecutionMode
diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine.cs
index a7d715ff..3e03d5a0 100644
--- a/src/RulesEngine/RulesEngine.cs
+++ b/src/RulesEngine/RulesEngine.cs
@@ -399,9 +399,33 @@ private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams
_rulesCache.AddOrUpdateGlobalParamsDelegate(compileRulesKey, globalParamsDelegate);
}
- foreach (var rule in workflow.Rules.Where(c => c.Enabled))
+ var enabledRules = workflow.Rules.Where(c => c.Enabled).ToArray();
+ var compiledFuncs = new RuleFunc[enabledRules.Length];
+ if (_reSettings.EnableParallelRuleCompilation)
{
- dictFunc.Add(rule.RuleName, CompileRule(rule,workflow.RuleExpressionType, ruleParams, globalParamExp));
+ try
+ {
+ System.Threading.Tasks.Parallel.For(0, enabledRules.Length, i => {
+ compiledFuncs[i] = CompileRule(enabledRules[i], workflow.RuleExpressionType, ruleParams, globalParamExp);
+ });
+ }
+ catch (AggregateException ae)
+ {
+ // Preserve the serial-compilation contract: the first rule that fails
+ // to compile surfaces its own exception, not an AggregateException.
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ae.InnerExceptions[0]).Throw();
+ }
+ }
+ else
+ {
+ for (var i = 0; i < enabledRules.Length; i++)
+ {
+ compiledFuncs[i] = CompileRule(enabledRules[i], workflow.RuleExpressionType, ruleParams, globalParamExp);
+ }
+ }
+ for (var i = 0; i < enabledRules.Length; i++)
+ {
+ dictFunc.Add(enabledRules[i].RuleName, compiledFuncs[i]);
}
_rulesCache.AddOrUpdateCompiledRule(compileRulesKey, dictFunc);