diff --git a/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs b/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs
index 0ef11e6d8..1548daa0c 100644
--- a/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs
+++ b/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs
@@ -68,7 +68,7 @@ public abstract class Activity : IActivity, ISignalHandler
/// will execute synchronously, while activities of the kind will execute asynchronously.
///
[JsonIgnore]
- public bool RunAsynchronously
+ public bool? RunAsynchronously
{
get => this.GetRunAsynchronously();
set => this.SetRunAsynchronously(value);
diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs
index ff9bee7c5..f82b56787 100644
--- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs
+++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs
@@ -17,7 +17,7 @@ public static class ActivityPropertyExtensions
/// Gets a flag indicating whether this activity can be used for starting a workflow.
/// Usually used for triggers, but also used to disambiguate between two or more starting activities and no starting activity was specified.
///
- public static bool GetCanStartWorkflow(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(CanStartWorkflowPropertyName, () => false);
+ public static bool GetCanStartWorkflow(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(CanStartWorkflowPropertyName, () => false);
///
/// Sets a flag indicating whether this activity can be used for starting a workflow.
@@ -29,14 +29,17 @@ public static class ActivityPropertyExtensions
/// By default, activities with an of , or
/// will execute synchronously, while activities of the kind will execute asynchronously.
///
- public static bool GetRunAsynchronously(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(RunAsynchronouslyPropertyName, () => false);
+ public static bool? GetRunAsynchronously(this IActivity activity)
+ {
+ return activity.CustomProperties.GetValueOrDefault(RunAsynchronouslyPropertyName, defaultValueFactory: () => null);
+ }
///
/// Sets a flag indicating if this activity should execute synchronously or asynchronously.
/// By default, activities with an of , or
/// will execute synchronously, while activities of the kind will execute asynchronously.
///
- public static void SetRunAsynchronously(this IActivity activity, bool value) => activity.CustomProperties[RunAsynchronouslyPropertyName[0]] = value;
+ public static void SetRunAsynchronously(this IActivity activity, bool? value) => activity.CustomProperties[RunAsynchronouslyPropertyName[0]] = value;
///
/// Gets the source file and line number where this activity was instantiated, if any.
diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityFactory.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityFactory.cs
index 081550a0d..6d5169efb 100644
--- a/src/modules/Elsa.Workflows.Core/Services/ActivityFactory.cs
+++ b/src/modules/Elsa.Workflows.Core/Services/ActivityFactory.cs
@@ -2,6 +2,7 @@
using System.Text.Json.Nodes;
using Elsa.Expressions.Helpers;
using Elsa.Extensions;
+using Elsa.Workflows.Attributes;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
using Humanizer;
@@ -34,7 +35,18 @@ public class ActivityFactory : IActivityFactory
// 7) Pull out your boolean flags from the cleaned element
var canStartWorkflow = GetBoolean(cleanedElement, "canStartWorkflow");
- var runAsynchronously = GetBoolean(cleanedElement, "runAsynchronously");
+ var runAsynchronously = GetNullableBoolean(cleanedElement, "runAsynchronously");
+ if (runAsynchronously is null)
+ {
+ if (context.ActivityDescriptor.Attributes.OfType().FirstOrDefault() is { } taskActivityAttribute)
+ {
+ runAsynchronously = taskActivityAttribute.RunAsynchronously;
+ }
+ else
+ {
+ runAsynchronously = false;
+ }
+ }
// 8) If composite, setup
if (activity is IComposite composite)
@@ -197,4 +209,24 @@ public class ActivityFactory : IActivityFactory
return false;
}
+
+ private static bool? GetNullableBoolean(JsonElement element, string propertyName)
+ {
+ var propertyNames = new[] { propertyName.Camelize(), propertyName.Pascalize() };
+
+ foreach (var name in propertyNames)
+ {
+ if (element.TryGetProperty("customProperties", out var customPropertyElement))
+ {
+ if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
+ return (bool?)canStartWorkflowElement.GetValue();
+ }
+
+ if (element.TryGetProperty(propertyName.Camelize(), out var property)
+ && (bool?)property.GetValue() is { } propValue)
+ return propValue;
+ }
+
+ return null;
+ }
}
diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs
index e744870a3..30277beb9 100644
--- a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs
@@ -1,6 +1,7 @@
using System.Text.Json;
using Elsa.Extensions;
using Elsa.Mediator.Contracts;
+using Elsa.Workflows.Attributes;
using Elsa.Workflows.CommitStates;
using Elsa.Workflows.Middleware.Activities;
using Elsa.Workflows.Models;
@@ -103,7 +104,28 @@ public class BackgroundActivityInvokerMiddleware(
return !GetIsBackgroundExecution(context)
&& context.WorkflowExecutionContext.ExecuteDelegate == null
- && (kind is ActivityKind.Job || (kind == ActivityKind.Task && activity.GetRunAsynchronously()));
+ && (kind is ActivityKind.Job || GetTaskRunAsynchronously(context));
+ }
+
+ private static bool GetTaskRunAsynchronously(ActivityExecutionContext context)
+ {
+ var activity = context.Activity;
+ var activityDescriptor = context.ActivityDescriptor;
+ var kind = activityDescriptor.Kind;
+
+ if (kind is not ActivityKind.Task)
+ return false;
+
+ var runAsynchronously = activity.GetRunAsynchronously();
+
+ if (runAsynchronously is null)
+ {
+ var taskActivityAttribute = activityDescriptor.Attributes.OfType().FirstOrDefault();
+
+ return taskActivityAttribute is { RunAsynchronously: true };
+ }
+
+ return (bool)runAsynchronously;
}
private static bool GetIsBackgroundExecution(ActivityExecutionContext context) => context.TransientProperties.ContainsKey(BackgroundActivityExecutionContextExtensions.IsBackgroundExecution);