Improving behaviour of Run Asynchronously property for the TaskActivityAttribute

This commit is contained in:
lucas.hipolito 2025-08-05 13:50:35 +02:00
parent dd8a6f5866
commit 1bf9b11ffc
4 changed files with 63 additions and 6 deletions

View file

@ -68,7 +68,7 @@ public abstract class Activity : IActivity, ISignalHandler
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
/// </summary>
[JsonIgnore]
public bool RunAsynchronously
public bool? RunAsynchronously
{
get => this.GetRunAsynchronously();
set => this.SetRunAsynchronously(value);

View file

@ -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.
/// </summary>
public static bool GetCanStartWorkflow(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(CanStartWorkflowPropertyName, () => false);
public static bool GetCanStartWorkflow(this IActivity activity) => activity.CustomProperties.GetValueOrDefault<bool>(CanStartWorkflowPropertyName, () => false);
/// <summary>
/// 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 <see cref="ActivityKind"/> of <see cref="Action"/>, <see cref="Task"/> or <see cref="Trigger"/>
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
/// </summary>
public static bool GetRunAsynchronously(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(RunAsynchronouslyPropertyName, () => false);
public static bool? GetRunAsynchronously(this IActivity activity)
{
return activity.CustomProperties.GetValueOrDefault<bool?>(RunAsynchronouslyPropertyName, defaultValueFactory: () => null);
}
/// <summary>
/// Sets a flag indicating if this activity should execute synchronously or asynchronously.
/// By default, activities with an <see cref="ActivityKind"/> of <see cref="Action"/>, <see cref="Task"/> or <see cref="Trigger"/>
/// will execute synchronously, while activities of the <see cref="ActivityKind.Job"/> kind will execute asynchronously.
/// </summary>
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;
/// <summary>
/// Gets the source file and line number where this activity was instantiated, if any.

View file

@ -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<TaskActivityAttribute>().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;
}
}

View file

@ -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<TaskActivityAttribute>().FirstOrDefault();
return taskActivityAttribute is { RunAsynchronously: true };
}
return (bool)runAsynchronously;
}
private static bool GetIsBackgroundExecution(ActivityExecutionContext context) => context.TransientProperties.ContainsKey(BackgroundActivityExecutionContextExtensions.IsBackgroundExecution);