0

我正在努力实现以下目标:

  1. 启动一个可能会运行很长时间的工作流,其中包括自定义活动;
  2. 这些自定义活动将遵循创建书签并等待其恢复的模式;
  3. 返回启动的工作流 id 并将其存储在某处;
  4. 在稍后的某个时间点,从其存储的 id 加载工作流;
  5. 检查工作流程是否完成,如果没有,检查是否有任何被阻止的书签;
  6. 恢复这些书签,从而导致相关活动完成,最终导致整个工作流程完成。

一个简单的用例是:审查文档并批准或拒绝它的工作流程。将为此创建一个工作流,通知个人,并且,只要他们愿意,他们可以通过批准或拒绝审查来提供反馈。

我以 Andrew Zhu 的代码为例,可在http://xhinker.com/post/WF4.aspx获得。

我遇到的问题是:

  1. 当使用像我描述的那样的自定义活动时,在启动工作流时,WaitForRunnableInstance会无限期地等待,所以我从来没有收到通知我的工作流已经开始并被持久化到数据库中;
  2. 确实存储了工作流,并且将列BlockingBookmarks设置为我的自定义活动的 id,ExecutionStatus设置为空闲,IsInitialized设置为 1,IsSuspended设置为 0,IsCompleted设置为 0 并IsReadyToRun设置为 0。

我已经开始在 Microsoft 论坛上进行讨论,可以在http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/6262874d-3493-4be1-bd05-b990307e1875/上看到并得到一些反馈,但还是有些不对劲。

对此有什么想法吗?对于带有自定义活动的长时间运行的工作流有什么有用的模式吗?

谢谢!

4

1 回答 1

5

这通常是长时间运行的工作流的最小示例,它在控制台上等待用户输入。(此代码从未执行,仅作为示例)

/// Activity that waits on bookmark for
/// someone to send it some text
///
public sealed class ReadLine: NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

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

    protected override void Execute(NativeActivityContext context)
    {
        context.CreateBookmark(
            BookmarkName.Get(context), 
            new BookmarkCallback(OnReadComplete));
    }

    void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state)
    {
        context.SetValue(base.Result, state as string);
    }
}

/// Program that uses ReadLine activity's bookmark to persist
/// workflow and waits for user input to resume it
///
public class Program
{
    static InstanceStore InstanceStore;
    static Activity Activity = GetExampleActivity();
    static AutoResetEvent unloadEvent = new AutoResetEvent(false);
    static Guid WfId;
    static WorkflowApplication WfApp;

    const string READ_LINE_BOOKMARK = "ReadLineBookMark";

    static void Main()
    {
        CreateInstanceStore();
        CreateWorkflowApp();

        // Start workflow application and wait for input
        StartAndUnload();

        //Get user input and send it to ReadLine bookmark reviving workflow
        GetInputAndComplete();
    }

    static void StartAndUnload()
    {
        WfApp.Run();
        WfId = app.Id;
        // !! Workflow will go idle on bookmark, no need to call Unload()
        unloadEvent.WaitOne();
    }

    static void GetInputAndComplete() 
    {
        var input = Console.ReadLine();

        // We've the text input, let's resume this thing
        WfApp.Load(WfId);
        WfApp.ResumeBookmark(READ_LINE_BOOKMARK, input);

        unloadEvent.WaitOne();
    }

    static void CreateInstanceStore()
    {
        InstanceStore = new SqlWorkflowInstanceStore("connection string");

        var handle = InstanceStore.CreateInstanceHandle();            
        var view = InstanceStore.Execute(
            handle, 
            new CreateWorkflowOwnerCommand(), 
            TimeSpan.FromSeconds(5));            

        handle.Free();

        InstanceStore.DefaultInstanceOwner = view.InstanceOwner;
    }

    static void CreateWorkflowApp()
    {
        WfApp = new WorkflowApplication(Activity)
        {
            InstanceStore = InstanceStore,
        };

        WfApp.PersistableIdle = (e) => { return PersistableIdleAction.Unload; }
        WfApp.Unloaded = (e) => 
        { 
            Console.WriteLine("WF App Unloaded\n");
            unloadEvent.Set(); 
        };
        WfApp.Completed = (e) =>
        {
            Console.WriteLine("\nWF App Ended: {0}.", e.CompletionState);
        };
    }

    static Activity GetExampleActivity()
    {
        var response = new Variable<string>();

        return return new Sequence()
        {
            Variables = { response },
            Activities = 
            { 
                new WriteLine()
                { 
                    Text = new InArgument<string>("Type some word:") 
                },
                new ReadLine() 
                { 
                    BookmarkName = READ_LINE_BOOKMARK, 
                    Result = new OutArgument<string>(response)
                },
                new WriteLine()
                {
                    Text = new InArgument<string>((context) => "You've typed: " + response.Get(context))
                }
            }
        };
    }

话虽如此,请考虑使用 IIS 和 AppFabric,您不会后悔的。AppFabric 只需单击六次,就可以处理在 WF 中实现的两个必须痛苦的事情:持久性和监控。如果您选择此路径,您将永远不需要编写下面的代码。

将您的工作流部署为 WCF 应用程序,您只需将其称为任何其他 WCF 合同。您有OperationContracts,它们是接收活动(如果花费太长时间则等待并坚持的活动)和相应的发送活动(将值返回给客户端的活动)。你甚至有它们之间的相关性的概念。AppFabric 负责恢复工作流,只需将先前初始化的关联句柄传递给它。

AppFabric 为您提供了一个配置 UI 来配置持久性存储、监控和其他选项,例如在空闲和/或持续多长时间之前。

您可以可视化 Active/Idle/Suspended 工作流、监控数据等。

于 2012-09-11T14:53:57.653 回答