工作流能够使用方法和事件通过消息与宿主程序交互。 事件用于将数据发送到工作流,而工作流使用方法将数据发送到主机应用程序。这篇中我们说明了工作流如何使用本地服务来调用外部方法,接下来我们用一个猜数字游戏的小例子来主要说明在本地服务中如何使用事件。调用外部方法和事件处理基本的流程差不多。
[置顶]坚持学习WF文章索引
一:先来介绍两个活动 EventDrivenActivity和 ListenActivity。
EventDrivenActivity是一个等侍事件触发的容器,EventDrivenActivity第一个子结点必需是一个继承 IEventActivity接口的Activity,后面所有的结点可以是任意Activity。该活动必须具有父级活动,它的父级活动可以是ListenActivity,StateActivity或StateMachineWorkflowActivity。
如果EventDrivenActvity在状态机工作流中使用,还有如下使用限制:
1. EventDrivenActivity 可以并且只能包含一个实现了IEventActivity接口的Activity.如HandleExternalEvent或Delay。
2. 如果有 HandleExternalEventActivity 必须是第一个结点.这是因为,Windows Workflow Foundation 实现的状态机工作流模型一次只处理一个EventDrivenActivity活动。例如,如果EventDrivenActivity 活动中包含多个能够运行的 IEventActivity 活动,则会出现以下情况:某个 EventDrivenActivity 活动在等待可能永远不会执行的IEventActivity 时被阻止。这样,状态机将无法处理任何其他消息。
3. 一个StateActivity状态容器可以有多个EventDrivenActivity。
ListenActivity属于单线触发容器,当某条分支中的结点执行完成后,该ListenActivity结点就结束,继续向下执行,其他分支内的结点就不执行了。EventDrivenActivity是它唯一可以添加的子活动,并且至少必须有两个子活动。该活动无法用于状态机工作流中。
二:工作流能够使用方法和事件通过消息与宿主程序交互。 事件用于将数据发送到工作流,而工作流使用方法将数据发送到主机应用程序。在坚持学习WF(8):本地服务之调用外部方法这篇中我们说明了工作流如何使用本地服务来调用外部方法,接下来我们用一个猜数字游戏的小例子来主要说明在本地服务中如何使用事件。调用外部方法和事件处理基本的流程差不多。
1.实现事件参数类,GuessReceivedEventArgs类需要继承自ExternalDataEventArgs类,ExternalDataEventArgs类中的instanceId可以保证正确的工作流实例来接收事件。
GuessReceivedEventArgs .cs
using System;
using System.Workflow.Activities;
namespace GuessNumber
{
[Serializable]
public class GuessReceivedEventArgs : ExternalDataEventArgs
{
private Int32 _nextGuess;
public GuessReceivedEventArgs(Guid instanceId, Int32 nextGuess)
: base(instanceId)
{
_nextGuess = nextGuess;
}
public Int32 NextGuess
{
get { return _nextGuess; }
set { _nextGuess = value; }
}
}
}
2.定义服务接口,代码如下
IGuessingGame.cs
using System;
using System.Workflow.Activities;
namespace GuessNumber
{
[ExternalDataExchange]
public interface IGuessingGame
{
void SendMessage(String message);
event EventHandler<GuessReceivedEventArgs> GuessReceived;
}
}
3.实现本地服务,先定义一个事件参数。这个也继承自ExternalDataEventArgs,虽然它不是用在工作流中,但是我们为了使用InstanceId,所以也让它继承这个类。
MessageReceivedEventArgs .cs
using System;
using System.Workflow.Activities;
namespace GuessNumber
{
[Serializable]
public class MessageReceivedEventArgs : ExternalDataEventArgs
{
private String _message;
public MessageReceivedEventArgs(Guid instanceId, String message)
: base(instanceId)
{
_message = message;
}
public String Message
{
get { return _message; }
set { _message = value; }
}
}
}
本地服务类,这类中共有四个成员:
SendMessage:workflow使用,由工作流实例来调用,发送一个信息到宿主程序,并且激发MessageReceive事件。
GuessReceived:workflow使用,接收下一个猜的数字,当OnGuessReceived方法调用的时候激发。
MessageReceived:宿主使用,从工作流实例接收一个信息,通过SendMessage方法激发。
OnGussReceived:宿主使用,发送一个新的猜的数字到工作流,会引发GuessReceived事件。
注意:SendMessage方法使用静态的WorkflowEnvironment.WorkflowInstanceId方法取回工作流实例的ID,WorkflowEnvironment表示正在当前线程中运行的工作流实例的事务环境。
GuessingGameService.cs
using System;
using System.Workflow.Runtime;
namespace GuessNumber
{
public class GuessingGameService : IGuessingGame
{
public void SendMessage(string message)
{
if (MessageReceived != null)
{
MessageReceivedEventArgs args
= new MessageReceivedEventArgs(
WorkflowEnvironment.WorkflowInstanceId,
message);
MessageReceived(this, args);
}
}
public event EventHandler<GuessReceivedEventArgs> GuessReceived;
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
public void OnGuessReceived(GuessReceivedEventArgs args)
{
if (GuessReceived != null)
{
//must pass null as the sender otherwise
//the correct workflow won't receive the event.
GuessReceived(null, args);
}
}
}
}
4.设计工作流,在工作流的Initialized事件中初始化这个要猜的随机数。
private void OnInitialized(object sender, EventArgs e)
{
Random random = new Random();
_theNumber = random.Next(1, 10);
Message = "Please guess a number between 1 and 10.";
}
Message属性给宿主的程序的暗示信息,指示猜的数字是大了还是小了。IsComplete属性是WhileActivity使用的,决定工作流是否完成了。
通过活动CallExternalMethodActivity设置InterfaceType和MethodName,还有message属性。
GuessReceived事件:在GuessReceived被激发的时候执行handleExternalEventActivity1_Invoked。该事件中决定用户的猜测是大了还是小了,为了完成工作流你必须处理该事件永远不发生的情况,我们要拖一个CodeActivity在右边的Event-Driven中,设置事件为一分钟,然后在拖一个TeminalActivity,如果一分钟内没猜的话就结束了。
完整的工作流如下图:
完整代码如下
GuessingGameWorkflow.cs
using System;
using System.Workflow.Activities;
namespace GuessNumber
{
/**//// <summary>
/// The guessing game workflow
/// </summary>
public sealed partial class GuessingGameWorkflow
: SequentialWorkflowActivity
{
Variables and Properties#region Variables and Properties
private Int32 _theNumber;
private Boolean _isComplete = false;
private String _message = String.Empty;
public String Message
{
get { return _message; }
set { _message = value; }
}
public Boolean IsComplete
{
get { return _isComplete; }
set { _isComplete = value; }
}
#endregion
public GuessingGameWorkflow()
{
InitializeComponent();
}
private void OnInitialized(object sender, EventArgs e)
{
Random random = new Random();
_theNumber = random.Next(1, 10);
Message = "请输入1到10之间的某一个数字.";
}
private void handleExternalEventActivity1_Invoked(
object sender, ExternalDataEventArgs e)
{
GuessReceivedEventArgs eventArgs
= e as GuessReceivedEventArgs;
if (eventArgs != null)
{
if (eventArgs.NextGuess < _theNumber)
{
Message = "请输入一个更大的数字.";
}
else if (eventArgs.NextGuess > _theNumber)
{
Message = "请输入一个更小的数字.";
}
else
{
Message = String.Format(
"恭喜您,猜对了{0}.", _theNumber);
IsComplete = true;
}
}
}
}
}
实现宿主程序,代码如下
Form1.cs
using System;
using System.Windows.Forms;
using System.Workflow.Activities;
using System.Workflow.Runtime;
namespace GuessNumber
{
/**//// <summary>
/// The WinForm for the number guessing game
/// </summary>
public partial class Form1 : Form
{
private WorkflowRuntimeManager _workflowManager;
private GuessingGameService _gameService;
private Guid _instanceId = Guid.Empty;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//create workflow runtime and manager
_workflowManager = new WorkflowRuntimeManager(
new WorkflowRuntime());
//add the external data exchange service to the runtime
ExternalDataExchangeService exchangeService
= new ExternalDataExchangeService();
_workflowManager.WorkflowRuntime.AddService(exchangeService);
//add our local service
_gameService = new GuessingGameService();
exchangeService.AddService(_gameService);
//subscribe to the service event that sends us messages
_gameService.MessageReceived
+= new EventHandler<MessageReceivedEventArgs>(
gameService_MessageReceived);
//handle the terminated event
_workflowManager.WorkflowRuntime.WorkflowTerminated
+= new EventHandler<WorkflowTerminatedEventArgs>(
WorkflowRuntime_WorkflowTerminated);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
//cleanup the workflow runtime
if (_workflowManager != null)
{
_workflowManager.Dispose();
}
}
private void btnStart_Click(object sender, EventArgs e)
{
//start the workflow without any parameters
_workflowManager.StartWorkflow(
typeof(GuessingGameWorkflow), null);
//start the workflow that uses custom activities
//generated by the WCA utility
//_workflowManager.StartWorkflow(
// typeof(GuessingGameWcaWorkflow), null);
btnGuess.Enabled = true;
}
private void btnGuess_Click(object sender, EventArgs e)
{
//pass the guess to the running workflow
try
{
Int32 nextGuess = Int32.Parse(txtNextNumber.Text);
//raise the GuessReceived event in the game service
_gameService.OnGuessReceived(
new GuessReceivedEventArgs(_instanceId, nextGuess));
}
catch (FormatException)
{
MessageBox.Show("请输入数字");
}
catch (OverflowException)
{
MessageBox.Show("数字超出范围");
}
catch (EventDeliveryFailedException)
{
MessageBox.Show(
"游戏已经结束,请重新开始游戏.",
"游戏结束", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
}
private delegate void UpdateDelegate();
private void gameService_MessageReceived(object sender,
MessageReceivedEventArgs e)
{
UpdateDelegate theDelegate = delegate()
{
//update the UI with the message
lblMessage.Text = e.Message;
txtNextNumber.SelectAll();
txtNextNumber.Focus();
};
//save the workflow instance Id. we will need it
//when we make the return trip with a guess.
_instanceId = e.InstanceId;
//execute the anonymous delegate on the UI thread
this.Invoke(theDelegate);
}
void WorkflowRuntime_WorkflowTerminated(object sender,
WorkflowTerminatedEventArgs e)
{
MessageBox.Show(
"对不起,时间已到.",
"重新开始游戏", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
宿主程序使用的windows窗体,最终效果如下图:
三:使用CallExternalMethodActivity和HandleExternalEventActivity可以和本地服务通信,但是我们需要设置他们的InterfaceType,MethodName或EventName。wca.exe是一个命令行工具,包含在windows SDK中,使用wca.exe,你可以生成一组强类型的自定义活动。这个命令行工具叫Workflow communication activity generator 。具体的参数可以在MSDN上查阅到。我们以上面例子来生成自定义通信活动(Wca /collapseArgs filepath.dll[.exe]),如下图:
该工具会自动查找.dll或.exe中标有[ExternalDataExchange]的接口,结束后会生成接口名+Invokes.cs和接口名+ Sinks.cs 的两个文件,IGuessingGame.Invokes.cs和IGuessingGame.Sinks.cs。第一个自定义活动SendMessage使用CallExternalMethodActivity作为基类,第二个自定义活动GuessReceived 使用HandleExternalEventActivity作为基类,然后我们就可以把这两个文件加入到项目中,重新生成就会在工具箱中出现了.
修改工作流宿主
在上面例子的基础上删除callExternalMethodActivity1和callExternalMethodActivity2。用刚才生成的SendMessage活动代替,绑定message参数属性。用GuessReceived代替handleExternalEventActivity1,绑定invoke事件即可。运行工作流效果和上面的是一样的。
上一篇:坚持学习WF(8):本地服务之调用外部方法