You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Elsa 3.5自定义阻塞活动发送刺激后仍无法恢复的技术问询

Elsa 3.5 动态阻塞活动恢复问题

环境

  • Elsa Workflows: 3.5.0
  • 使用的包:
<PackageReference Include="Elsa.Workflows.Core" Version="3.5.0" />
<PackageReference Include="Elsa.Workflows.Runtime" Version="3.5.0" />
<PackageReference Include="Elsa.Workflows.Api" Version="3.5.0" />
<PackageReference Include="Elsa.EntityFrameworkCore" Version="3.5.0" />
<PackageReference Include="Elsa.Stimuli" Version="3.5.0" />
  • .NET版本: 8.0
  • 数据库: SQL Server
  • 托管方式: ASP.NET Core Web API

已执行操作

动态活动提供者

public async Task<IEnumerable<Elsa.Workflows.Models.ActivityDescriptor>> GetActivityDescriptorsAsync(
    CancellationToken cancellationToken = default)
{
    var activities = await GetActiveActivitiesAsync(cancellationToken);
    var descriptors = new List<Elsa.Workflows.Models.ActivityDescriptor>();

    foreach (var activity in activities)
    {
        try
        {
            var descriptor = ConvertToActivityDescriptor(activity);
            descriptors.Add(descriptor);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error converting activity {activity.TypeName} to descriptor: {ex.Message}");
        }
    }

    return descriptors;
}

private Elsa.Workflows.Models.ActivityDescriptor ConvertToActivityDescriptor(DynamicActivity activity)
{
    var descriptor = new Elsa.Workflows.Models.ActivityDescriptor
    {
        TypeName = activity.TypeName,
        Namespace = "ElsaServer.Dynamic",
        Category = "Catt",
        DisplayName = "event",
        IsStart = false,
        Name = activity.TypeName,
        Constructor = context =>
        {
            var dynamicTaskActivity = _activityFactory.Create<CustomBlockingActivity>(context);
            dynamicTaskActivity.Type = activity.TypeName;
            return dynamicTaskActivity;
        }
    };

    return descriptor;
}

在Program.cs中的注册

services.AddScoped<DatabaseActivityProvider>();
services.AddScoped<IActivityProvider, DatabaseActivityProvider>(sp => sp.GetRequiredService<DatabaseActivityProvider>());

自定义阻塞活动

public class CustomBlockingActivity : Activity
{
    protected override void Execute(ActivityExecutionContext context)
    {
        context.CreateBookmark("event");
    }
}

已尝试的方案

1. 使用Stimulus Sender恢复

[HttpPost("complete-Task-With-Stimulus-sender")]
public async Task<IActionResult> CompleteTaskLastWithStimulusSender(CancellationToken cto)
{
    var stimulus = "myevent";
    var activityTypeName = ActivityTypeNameHelper.GenerateTypeName<CustomBlockingActivity>();
    await _stimulussender.SendAsync(activityTypeName, stimulus);
    return Ok();
}

2. 使用IWorkflowRunner手动恢复工作流

[HttpPost("complete-Task-With-Stimulus-sender")]
public async Task<IActionResult> CompleteTaskLastWithStimulusSender(CancellationToken cto)
{
    var workflowRunner = _serviceProvider.GetRequiredService<IWorkflowRunner>();

    var activity = new CustomBlockingActivity();

    var activityHandle = ActivityHandle.FromActivityInstanceId("6c0a84c5be9f79b5");

    var runOptions = new RunWorkflowOptions
    {
        WorkflowInstanceId = "2595cfecc226fe97",
        BookmarkId = "11cdad48ae57be4e",
        ActivityHandle = activityHandle,
        Input = new Dictionary<string, object>()
    };

    var result = await workflowRunner.RunAsync(activity, runOptions, cto);

    return Ok(new
    {
        message = "Task completed using IActivity with WorkflowRunner"
    });
}

两种方案结果一致:工作流未恢复,书签仍存在于数据库中。

问题

是否遗漏了Elsa处理阻塞活动恢复的某些步骤?或者该场景在Elsa 3.5中不被支持?

如果存在正确恢复自定义阻塞活动或关联动态活动书签与特定刺激的其他方法,正确的实现方式是什么?


解决方案

1. 核心问题:书签匹配不匹配

你当前的实现存在两个关键问题:

  • 发送刺激时使用的是CustomBlockingActivity的类型名,但工作流中实际活动的类型名是动态生成的activity.TypeName,导致刺激无法匹配到对应书签。
  • 创建书签时未指定匹配规则,Elsa默认需要刺激与书签的ActivityTypeName、Hash完全匹配。

2. 修正Stimulus发送逻辑

发送刺激时必须使用动态活动的实际类型名,而非CustomBlockingActivity的类型名:

[HttpPost("complete-Task-With-Stimulus-sender")]
public async Task<IActionResult> CompleteTaskLastWithStimulusSender(string dynamicActivityTypeName, CancellationToken cto)
{
    var stimulus = "myevent";
    // 使用动态活动的类型名,而非CustomBlockingActivity的类型名
    await _stimulussender.SendAsync(dynamicActivityTypeName, stimulus);
    return Ok();
}

3. 优化书签创建逻辑

在创建书签时,将动态活动的Type属性纳入匹配规则,确保哈希值与动态类型关联:

public class CustomBlockingActivity : Activity
{
    public Input<string> Type { get; set; } = default!;

    protected override void Execute(ActivityExecutionContext context)
    {
        var bookmarkOptions = new BookmarkOptions
        {
            // 用动态活动的Type生成哈希,确保每个动态类型的书签唯一
            Hash = context.GenerateHash(Type.Get(context)),
            Payload = new { EventType = Type.Get(context) }
        };
        context.CreateBookmark("event", bookmarkOptions);
    }
}

4. 手动恢复的正确方式

使用IWorkflowInstanceExecutor替代IWorkflowRunner,直接通过工作流实例ID和书签ID恢复:

[HttpPost("complete-Task-Manual")]
public async Task<IActionResult> CompleteTaskManual(string workflowInstanceId, string bookmarkId, CancellationToken cto)
{
    var executor = _serviceProvider.GetRequiredService<IWorkflowInstanceExecutor>();
    var result = await executor.ResumeBookmarkAsync(workflowInstanceId, bookmarkId, new Dictionary<string, object>(), cto);
    return Ok(new { message = "工作流已恢复", result.Succeeded });
}

5. 自定义书签解析器(可选)

如果需要更灵活的匹配规则(比如根据刺激内容匹配),可以实现IBookmarkResolver:

public class CustomBookmarkResolver : IBookmarkResolver
{
    public ValueTask<IEnumerable<BookmarkMatch>> ResolveAsync(BookmarkResolutionContext context)
    {
        var stimulus = context.Stimulus.ToString();
        var matches = context.Bookmarks
            .Where(b => b.Payload is { EventType: string eventType } && eventType == stimulus)
            .Select(b => new BookmarkMatch(b, 1));
        
        return ValueTask.FromResult(matches);
    }
}

在Program.cs注册:

services.AddScoped<IBookmarkResolver, CustomBookmarkResolver>();

关键结论

Elsa 3.5完全支持动态阻塞活动场景,问题出在书签匹配的配置上。只要确保刺激的目标类型名与动态活动的类型名一致,同时书签的哈希/匹配规则与刺激对应,即可正常恢复工作流。

内容的提问来源于stack exchange,提问作者Loai Ismail

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 01:40:59