[置顶]坚持学习WF文章索引
WF 提供的通信模型是构建于队列系统的基础之上,我们可以使用自定义活动来注册以接收关于队列的消息,而宿主应用程序中的服务则发送关于队列的消息。自定义活动可以使用此模型来处理外部事件,也可以传递异步活动执行的完成。这样,您的活动可以先执行到某一点,然后等待激发因素的到来以便继续执行。下图描述了宿主应用程序中的代码与工作流中的代码(或活动)之间的通信模型。
下面这张图是WF类库中和队列相关的三个类:
为了使自定义活动能够侦听消息是否到达某个队列,我们通常有以下步骤:
1.使用WorkflowQueuingService 创建工作流队列,该类还提供了创建、查找或删除工作流队列所需的方法。一般我们会在自定义活动的 Initialize 或 Execute 方法中来实现。
2.自定义活动必须注册才能接收到这些通知,方法是在工作流队列自身中注册 QueueItemAvailable 事件。您可以使用 RegisterForQueueItemAvailable 方法为 QueueItemAvailable 事件注册一个订户。QueueItemAvailable事件用于通知订户项已经传送(以异步方式)至此 WorkflowQueue。在确保队列存在并注册事件后,当队列中有可用项目时,您的活动会得到通知,之后,您可以从队列中取出该项目并对其进行处理。
3.我们自定义活动要能够充当事件接收器的活动(如 HandleExternalEvent 活动),您还需要实现 IEventActivity 接口。如果您的活动要侦听事件,则此接口用于定义该活动的主要职责:
public interface IEventActivity
{
void Subscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler);
void Unsubscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler);
IComparable QueueName { get; }
}
WF中所有的通信的活动都实现了这个接口。
QueueName 属性必须返回 IComparable 值,消息加入队列时,它可以唯一地标识您的活动。对于用于将消息加入队列以通知工作流运行时的代码,也需要使用这同一个队列名。
通过此接口,能够命令活动在其执行前订阅事件并让活动知道何时取消订阅。在订阅和取消订阅方法中,该活动负责确保使用 QueueName 来创建队列并在处理结束时删除队列。此外,这也为您的活动能够向任何本地服务注册信息提供了机会,这些本地服务将代表活动来执行逻辑并通过将消息加入队列予以响应。
本地服务是您定义并从主机添加到工作流运行时的一个类,它可以被您的宿主代码、工作流或您的活动所利用。只要宿主应用程序处于运行状态,本地服务就能够维护事件处理程序或其他侦听程序,从而可通过将消息加入队列来确保相应的数据到达工作流。您传递给本地服务的信息应包括队列名和工作流实例 ID 的相关信息,以及该服务发起工作或向您的活动返回结果时所需的任何信息。
1.下面我们先来实现一个这样的自定义活动,利用该活动得到对列中的信息,然后在将该信息发送给宿主程序代码如下:
RequestResponseData.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Collections.Generic;
namespace CaryQueue
{
[Designer(typeof(SequentialActivityDesigner),typeof(IDesigner))]
public partial class RequestResponseData: SequenceActivity,IActivityEventListener<QueueEventArgs>,IEventActivity
{
public RequestResponseData()
{
InitializeComponent();
}
Properties#region Properties
public static DependencyProperty OutputValuesProperty = System.Workflow.ComponentModel.DependencyProperty.Register("OutputValues", typeof(Dictionary<string, string>), typeof(RequestResponseData));
[Description("The values to be sent back to the host")]
[Category("Data")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Dictionary<string, string> OutputValues
{
get
{
return ((Dictionary<string, string>)(base.GetValue(RequestResponseData.OutputValuesProperty)));
}
set
{
base.SetValue(RequestResponseData.OutputValuesProperty, value);
}
}
public static DependencyProperty InputValuesProperty = System.Workflow.ComponentModel.DependencyProperty.Register("InputValues", typeof(Dictionary<string, string>), typeof(RequestResponseData));
[Description("The data sent to the activity")]
[Category("Data")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Dictionary<string, string> InputValues
{
get
{
return ((Dictionary<string, string>)(base.GetValue(RequestResponseData.InputValuesProperty)));
}
set
{
base.SetValue(RequestResponseData.InputValuesProperty, value);
}
}
#endregion
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
if (ProcessMessage(GetQueue(executionContext, QueueName)))
{
//let the base class run the children, and return status
return base.Execute(executionContext);
}
//if no items are there, then subscribe to get notified when they arrive
Subscribe(executionContext, this);
return ActivityExecutionStatus.Executing;
}
IEvent and IActivityListener#region IEvent and IActivityListener
[Browsable(false)]
public IComparable QueueName
{
get { return "CaryQueue"; }
}
public void Subscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
{
WorkflowQueue queue = parentContext.GetService<WorkflowQueuingService>().CreateWorkflowQueue(QueueName, false);
queue.RegisterForQueueItemAvailable(parentEventHandler);
}
public void Unsubscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
{
WorkflowQueue q = GetQueue(parentContext, QueueName);
if (q != null)
{
q.UnregisterForQueueItemAvailable(parentEventHandler);
parentContext.GetService<WorkflowQueuingService>().DeleteWorkflowQueue(QueueName);
}
}
public void OnEvent(object sender, QueueEventArgs e)
{
ActivityExecutionContext ctx = sender as ActivityExecutionContext;
if (ProcessMessage(GetQueue(ctx, e.QueueName)))
{
if (base.Execute(ctx) == ActivityExecutionStatus.Closed)
ctx.CloseActivity();
}
}
#endregion
private WorkflowQueue GetQueue(ActivityExecutionContext context, IComparable queueName)
{
WorkflowQueuingService qService = context.GetService<WorkflowQueuingService>();
if (qService != null && qService.Exists(queueName))
return qService.GetWorkflowQueue(queueName);
else
return null;
}
private bool ProcessMessage(WorkflowQueue queue)
{
if (queue == null || queue.Count == 0)
return false;
MessageHelper msg = queue.Peek() as MessageHelper;
if (msg != null && msg.InputValues != null)
{
InputValues = msg.InputValues;
Console.WriteLine("Request:"+msg.InputValues["inputvalueone"]);
Console.WriteLine("Request:" + msg.InputValues["inputvaluetwo"]);
return true;
}
return false;
}
/**//// <summary>
/// Called when the base class completes executing the
/// child activities. Here we know all the children are complete.
/// </summary>
/// <param name="executionContext"></param>
protected override void OnSequenceComplete(ActivityExecutionContext executionContext)
{
//pull the message from the queue and send the
//response back to the host, signalling we are done.
WorkflowQueue q = executionContext.GetService<WorkflowQueuingService>().GetWorkflowQueue(QueueName);
MessageHelper msg = q.Dequeue() as MessageHelper;
msg.SendResponse(OutputValues);
//clean up, we are done.
Unsubscribe(executionContext, this);
base.OnSequenceComplete(executionContext);
}
}
}
1.1.OutputValues和InputValues两个依赖属性,代表输入和输出参数。
1.2.重写了Execute方法,在这之中我们完成了创建队列,注册事件等操作。
1.3.ProcessMessage在该方法中我们得到队列的消息并输出,注意这个中我们使用的是Peek方法。
1.4.重写了OnSequenceComplete方法,并将队列中的信息保存在MessageHelper中。
2.一个辅助类MessageHelper.cs
MessageHelper .cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace CaryQueue
{
public class MessageHelper
{
ManualResetEvent resetEvent;
bool isResponse;
public MessageHelper()
{
resetEvent = new ManualResetEvent(false);
isResponse = false;
}
private Dictionary<string, string> input;
public Dictionary<string, string> InputValues
{
get { return input; }
set { input = value; }
}
private Dictionary<string, string> output;
public Dictionary<string, string> OutputValues
{
get { return output; }
set { output = value; }
}
public void SendResponse(Dictionary<string, string> returnValues)
{
OutputValues = returnValues;
this.isResponse = true;
resetEvent.Set();
}
public void WaitForResponse()
{
resetEvent.WaitOne();
resetEvent = null;
}
}
}
3.然后我们新建一个顺序工作流,添加我们自定义的活动,在自定义活动中拖一个CodeActivity,实现工作流 如下图:
完整代码如下:
Workflow1.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Collections.Generic;
namespace CaryQueue
{
public sealed partial class Workflow1: SequentialWorkflowActivity
{
public Workflow1()
{
InitializeComponent();
}
private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
}
private void codeActivity1_ExecuteCode_1(object sender, EventArgs e)
{
Dictionary<string, string> tmpOutput = new Dictionary<string, string>(requestResponseData1.InputValues.Count);
foreach (KeyValuePair<string, string> entry in requestResponseData1.InputValues)
tmpOutput.Add(entry.Key, String.Concat("Response:", entry.Value));
requestResponseData1.OutputValues = tmpOutput;
}
}
}
4.实现宿主程序,代码如下
Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
namespace CaryQueue
{
class Program
{
static void Main(string[] args)
{
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
//add services to the runtime
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(CaryQueue.Workflow1));
instance.Start();
Dictionary<string, string> inputs = new Dictionary<string, string>();
inputs["inputvalueone"] = "inputvalueone";
inputs["inputvaluetwo"] = "inputvaluetwo";
//send data to receive activity using work item
EnqueuePendingWorkItem workItem = new EnqueuePendingWorkItem();
//send two way message to worklow and wait for response
MessageHelper msg = new MessageHelper();
msg.InputValues = inputs;
//instance.EnqueueItemOnIdle("RequestResponseData:SecondInput", msg, null, null);
instance.EnqueueItemOnIdle("CaryQueue", msg, workItem, "");
workItem.WaitForCommit();
Console.WriteLine("Outcome of the submission: {0}", workItem.Success ? "Success" : "Failure");
msg.WaitForResponse();
Console.WriteLine(msg.OutputValues["inputvalueone"]);
Console.WriteLine(msg.OutputValues["inputvaluetwo"]);
//Console.WriteLine(msg.InputValues["inputvalueone"]);
//Console.WriteLine(msg.InputValues["inputvaluetwo"]);
//wait for the workflow to complete.
waitHandle.WaitOne();
}
}
}
}
在宿主程序中我们将inputs 参数加入工作流实例队列中,我们使用的是WrokflowInstance的EnqueueItemOnIdle方法,该类还包含另一个方法EnqueueItem,如下:
public void EnqueueItem(IComparable queueName, object item,
IPendingWork pendingWork, object workItem);
public void EnqueueItemOnIdle(IComparable queueName, object item,
IPendingWork pendingWork, object workItem);
两个方法都使用相同的参数,包括要排队的队列名称和对象。(我会简要介绍其他两个参数。)此时,实例 ID 和队列名称是如此重要的原因就显而易见了。为了获得 WorkflowInstance 对象,您需要实例 ID,而为了将数据排入该实例的队列,您需要队列名称。两种方法之间的差异在于它们如何将数据传送到队列。
EnqueueItem 方法试图将数据立即传送到队列,并假定工作流已到达已创建队列并在等待数据的那个点。在某些情况下,这正好是您希望发生的情况。在另一些情况下,这类行为可能会导致出现一种争用状况,即您的工作流尚未到达该活动可以创建您感兴趣的那个队列的点。您可以通过调用 GetWorkflowQueueData 并在队列就位之前一直检查来检查这种情况,但这非常麻烦。
EnqueueItemOnIdle 方法旨在解决这些争用状况。当您使用此方法将数据排入队列时,运行时在该工作流变成空闲前,不会尝试将数据传送到队列中(当目前执行的所有活动都在等待某种输入时)。这可以让宿主应用程序更加确定,工作流已为正在提交的数据做好准备,因为更有可能创建队列的活动已经开始执行,并且已真正创建了该队列。
在两个方法的第三个参数为IPendingWork ,该接口标识可添加到工作流的工作批并参与事务的类。workItem 参数是在提交或完成事务时,通过事务,您可以在消息已成功传送到队列且工作流已提交当前事务时获得通知。这是发挥这两个额外参数 EnqueueItem 和 EnqueueItemOnIdle 的作用的地方。该类在实现 IPendingWork 过程中可以使用的状态或数据。EnqueuePendingWorkItem是一个简单的类,它实现了 IPendingWork 接口,并可用来在数据被传送到队列时获得通知。代码如下:
EnqueuePendingWorkItem .cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Runtime;
using System.Threading;
namespace CaryQueue
{
public class EnqueuePendingWorkItem : IPendingWork
{
private ManualResetEvent resetEvent;
private bool result;
public bool Success
{
get { return result; }
set { result = value; }
}
/**//// <summary>
/// Allows the host to wait for the commit to know the
/// data is received and processing made it to a commit point.
/// </summary>
public void WaitForCommit()
{
if (resetEvent != null)
resetEvent.WaitOne();
else
throw new InvalidOperationException("This item has already been completed");
}
public EnqueuePendingWorkItem()
{
resetEvent = new ManualResetEvent(false);
}
public void Commit(System.Transactions.Transaction transaction, System.Collections.ICollection items)
{
//option to do work here using the workflow's tx
Console.WriteLine("Commit called on pending work item");
}
/**//// <summary>
/// SEts the outcome and signals completion.
/// </summary>
/// <param name="succeeded"></param>
/// <param name="items"></param>
public void Complete(bool succeeded, System.Collections.ICollection items)
{
Success = succeeded;
resetEvent.Set();
resetEvent = null;
}
public bool MustCommit(System.Collections.ICollection items)
{
//always return true to avoid hang scenarios if not
//using persistence.
return true;
}
}
}
5.工作流执行的结果如下:
Request:inputvalueone
Request:inputvaluetwo
Commit called on pending work item
Outcome of the submission: Success
Response:inputvalueone
Response:inputvaluetwo
请按任意键继续. . .