【问题标题】:Handling WF 4.0 long running activity using TPL使用 TPL 处理 WF 4.0 长时间运行的活动
【发布时间】:2025-11-27 07:30:02
【问题描述】:

我创建了一个执行 Web 请求并将结果存储到数据库中的活动。通常这个过程大约需要 1 个小时,它会使工作流引擎表现异常。我发现对于这些长时间运行的活动,我应该编写一些不同的代码,这样工作流引擎线程就不会被阻塞。

研究一些关于编写长期活动的博客,我知道我应该使用Bookmark 概念。但是我没有使用 TPL 和 Task 的任何解决方案。

此代码对于使用Tasks 处理长时间运行的活动是否正确?

public sealed class WebSaveActivity : NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        context.CreateBookmark("websave", (activityContext, bookmark, value) =>
        {

        });

        Task.Factory.StartNew(() =>
        {
            GetAndSave(); // This takes 1 hour to accomplish.
            context.RemoveBookmark("websave");
        });

    }

    protected override bool CanInduceIdle 
    {
        get
        {
            return true;
        }
    }
}

【问题讨论】:

  • StartNew应谨慎使用,考虑使用Run

标签: c# task-parallel-library workflow-foundation-4 workflow-foundation


【解决方案1】:

不,这不是书签的使用方式。当工作流必须等待来自外部流程的输入时,使用书签。

例如:我有一个文档审批工作流,在某个时间点,该工作流必须等待人工审阅者对文档给出 OK。当调用ResumeBookmark 时,工作流将被运行时闲置并再次激活,而不是将工作流实例保存在内存中。

在您的情况下,您的工作流不能闲置,因为它有一个在其上下文中运行的操作。该操作是您的任务,顺便说一句,它是一种即发即弃的任务,因此 WF 无法处理严重的故障。

现在,对于一个可能的解决方案,您可能会考虑让其他进程调用 GetAndSave 方法并让该进程最终调用 WF 上的 ResumeBookmark,以便运行时可以空闲工作流。该流程甚至可以与托管您的工作流程的流程相同。

有关示例,请参阅this blogpost。想象一下,您无需等待人类在控制台中输入内容,而是执行了长时间运行的任务。

您没有指定活动之后会发生什么,但请注意,当书签恢复时,可以将数据返回到工作流。因此,GetAndSave 的任何结果,即使它只是一个错误代码,您也可以使用它来决定如何进一步配合工作流程中的其他活动。

希望这对您有意义,并且您会看到我试图概述的可能的解决方案。

编辑 关于在 WF 中使用任务或异步/等待的快速说明。 AFAIK 没有方法可以覆盖返回的任务,因此您要么必须使用 .Wait().Result 阻止它们,要么忘记它。因为如果你不能等待它们,那么在工作流执行期间就会发生坏事,因为其他活动可能会在使用 Tasks 的活动完成其工作之前开始。

在开发 WF 运行时,Task 的整个概念还很年轻,因此 WF 运行时没有/不适合它们。

编辑 2: 示例实现(基于this excellent official documentation

您的活动几乎是空的:

public sealed class TriggerDownload : NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        // Create a Bookmark and wait for it to be resumed.
        context.CreateBookmark(BookmarkName.Get(context),
            new BookmarkCallback(OnResumeBookmark));
    }

    protected override bool CanInduceIdle
    {
        get { return true; }
    }

    public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
    {
       // When the Bookmark is resumed, assign its value to
       // the Result argument. (This depends on whether you have a result on your GetData method like a string with a result code or something)
       Result.Set(context, (string)obj);
    }
}

它向工作流运行时发出信号,表明工作流可以空闲以及如何恢复。

现在,对于工作流运行时配置:

WorkflowApplication wfApp = new WorkflowApplication(<Your WF>);

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);


wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
    idleEvent.Set();
};

// Run the workflow.
wfApp.Run();

// Wait for the workflow to go idle before starting the download
idleEvent.WaitOne();

// Start the download and resume the bookmark when finished.
var result = await Task.Run(() => GetAndSave());
BookmarkResumptionResult result = wfApp.ResumeBookmark(new Bookmark("GetData"), result);

// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine("BookmarkResumptionResult: {0}", result);

【讨论】:

  • 感谢您的回答,那么您对编写此类活动有何建议?
  • 这种方法需要修改我无权访问的工作流应用程序。我正在寻找一种解决方案来处理我的活动中的所有必需代码,而不是修改服务器。
  • 啊错过了问题中的关键部分 :-) 无论如何,所有内置机制,如书签或主机通信都是不可能的。只是不支持异步/等待,并且由于您无法在 WF 中使用任务而不使用阻塞等待来完成任务,因此无法创建需要很长时间但不会阻塞工作流线程的活动。
【解决方案2】:

我刚刚在这里看到了您的相关问题:How to write a long running activity to call web services in WF 4.0

另一种实现活动的方法是AsyncCodeActivity

namespace MyLibrary.Activities
{
    using System;
    using System.Activities;

    public sealed class MyActivity : AsyncCodeActivity
    {
        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            var delegateToLongOperation = new Func<bool>(this.LongRunningSave);
            context.UserState = delegateToLongOperation;
            return delegateToLongOperation.BeginInvoke(callback, state);
        }

        protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            var longOperationDelegate = (Func<bool>) context.UserState;
            var longOperationResult = longOperationDelegate.EndInvoke(result);

            // Can continue your activity logic here.
        }

        private bool LongRunningSave()
        {
            // Logic to perform the save.
            return true;
        }
    }
}

工作流实例保留在内存中,但至少工作流运行时可以处理其正常的调度任务,而不会有一个线程被长时间运行的进程占用。

【讨论】:

    最近更新 更多