我正在努力实现以下目标:
- 启动一个可能长时间运行的工作流,其中包括自定义活动
- 这些自定义活动将遵循创建书签并等待其简历的模式
- 返回已启动的工作流id并将其存储在某个位置
- 稍后,从存储的id加载工作流
- 检查工作流是否已完成,如果未完成,请检查是否有任何被阻止的书签
- 恢复这些书签,从而完成相关活动,最终完成整个工作流
一个简单的用例是:审查文档并批准或拒绝文档的工作流。将为此创建一个工作流,通知个人,无论何时,他们都可以通过批准或拒绝审查来提供反馈。
我以Andrew Zhu的代码为例,该代码可在http://xhinker.com/post/WF4.aspx.
我遇到的问题是:
- 当使用像我所描述的那样的自定义活动时,在启动工作流时,
WaitForRunnableInstance
会无限期等待,因此我永远不会收到工作流已启动并持久化到数据库的通知 - 工作流确实已存储,并且列
BlockingBookmarks
设置为我的自定义活动的id,ExecutionStatus
设置为Idle,IsInitialized
设置为1,IsSuspended
设置为0,IsCompleted
设置为0并且IsReadyToRun
设置为0
我已经开始在微软论坛上进行讨论,可以在http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/6262874d-3493-4be1-bd05-b990307e1875/并得到了一些反馈,但有些地方仍然不对劲。
对此有什么想法吗?对于具有自定义活动的长时间运行的工作流,有什么有用的模式吗?
谢谢!
这通常是长时间运行的工作流的最小示例,它等待控制台上的用户输入。(此代码从未执行过,仅以它为例)
/// 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 Unloadedn");
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,用于配置Persistence Store、监控和其他选项,如空闲和/或持久化之前的时间。
您可以可视化活动/空闲/暂停的工作流、监控数据等。