到目前我们知道如何创建一个工作流节点(activity)和使用最简单的方法来启动工作流,就是使用WorkflowInvoker 类。WorkflowInvoker.Invoke 方法比较简单,它属于同步调用,工作流启动的线程与调用程序的线程是一样的。
启动工作流另外一个方法是使用WorkflowApplication 类,相对WorkflowInvoke类,它可以允许工作流在另外一个独立线程运行,提供了当工作流结束的时候可以调用的delegates。比起不使用WF4,可以更加简单和快捷的创建多线程服务器或客户端程序。
这节会学习到,修改启动程序用WorkflowApplication来运行SayHello节点,并观察线程表现。要求可以达到以下功能:
- 返回一个个性化的问候语
- 返回一个非零的Int32数值,代表工作流运行的managed thread ID
我们还是使用TDD的先写测试方法的做法,看看如何完成以上的要求。
任务1 – 编写测试,验证可以将工作流线程ID作为输出参数返回
- 在HelloWorkflow.Tests项目下,打开SayHelloFixture.cs,并添加以下命名空间:
using System.Threading;
using System.Diagnostics;
- 添加ShouldReturnWorkflowThread 测试方法,代码如下:
/// <summary>
/// Verifies that the workflow returns an Out Argument
/// Name: WorkflowThread
/// Type: Int32
/// Value: Non-Zero
/// </summary>
[TestMethod]
public void ShouldReturnWorkflowThread ()
{
var output = WorkflowInvoker.Invoke(
new SayHello()
{
UserName = "Test"
});
Assert.IsTrue(output.ContainsKey("WorkflowThread "),
"SayHello must contain an OutArgument named WorkflowThread");
// Don't know for sure what it is yet
var outarg = output["WorkflowThread"];
Assert.IsInstanceOfType(outarg, typeof(Int32) ,
"WorkflowThread must be of type Int32");
Assert.AreNotEqual (0, outarg,
"WorkflowThread must not be zero");
Debug.WriteLine("Test thread is " +
Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("Workflow thread is " + outarg.ToString());
}
- 上述的代码测试了
- output字典里存在WorkflowThread输出参数
- 该参数值是一个Int32类型
- 该参数值不是0
- 按CTRL+R, A 运行所有测试。可以预料ShouldReturnWorkflowThread 将会失败,因为我们还没有添加WorkflowThread这个输出参数
任务 2 – 添加WorkflowThread输出参数
写好了测试方法后,要做的就是去实现功能使测试能够通过。
- 打开SayHello.xaml 并添加新的参数
- 参数WorkflowThread,方向Out ,类型Int32
到现在为止,SayHello只有一个Assign节点。我们需要2个Assign节点,一个是用于给Greeting赋值,另外一个是给WorkflowThread赋值。因此要用一个可以放置多个节点的容器节点来存放这两个Assign节点。可以有很多容器节点选择,但最简的是使用Sequence 节点。
- 在删除已有的Assign节点前,是不能直接拖动Sequence节点到设计器界面的。由于现在这个Assign需要保留,所以我们可以使用剪切的方式清除此节点。右键点击Assign节点,并选择Cut
- 从工具栏拖动Sequence 并放置于设计界面
- 在Sequence节点内部,右键点击并选择Paste 将之前复制好的Assign节点放到里面
- 由于要获取线程ID,所以要输入(Import )System.Threading 命名空间。在设计器下方,选择Imports 并添加System.Threading ,通过输入头几个字符来列出要添加的命名空间,找到需要的名称后按回车
- 不添加Imports,可以使用线程类方法,但必须在类的前面加上System.Threading ,例如System.Threading.Thread
- 从工具栏拖动Assign 节点到Sequence内,并完成对WorkflowThread的赋值
- To :WorkflowThread
- Value :Thread.CurrentThread.ManagedThreadID
- 按CTRL+SHIFT+B 编译
- 按CTRL+R,A 运行所有测试,应该全部能够通过。要查看Debug.Print输出结果,可以在Test Results窗口,双击ShouldReturnWorkflowThread ,会出现Debug输出窗口,在Debug Trace 部分将会显示Test thread和Workflow thread信息
- 很明显,因为WorkflowInvoke使用与调用程序相同的线程来运行工作流,这两个值将永远一样
任务 3 – 使用WorkflowApplication类来启动工作流,并获取工作流运行的线程ID
上述的测试有一个漏洞,如果WorkflowThread参数都是返回某个非零值,例如返回1而不是真正的线程ID,测试也能通过。所以我们需要修改这个测试并使之能够真正验证返回的线程ID就是工作流运行时的线程ID。
为了验证真正的线程ID,需要使用WorkflowApplication 运行工作流。我们修改测试方法,通过调用WorkflowApplication.Completed 动作(action)来获取工作流线程ID,并将之与返回参数WorkflowThread进行比较。
- 将上述的测试方法ShouldReturnWorkflowThread 改为ShouldReturnWorkflowThreadInvoke
- 复制以下代码创建新的ShouldReturnWorkflowThread 方法:
/// <summary>
/// Verifies that the workflow returns an Out Argument
/// Name: WorkflowThread
/// Type: Int32
/// Value: Non-Zero, matches thread used for Completed action
/// </summary>
[TestMethod]
public void ShouldReturnWorkflowThread()
{
AutoResetEvent sync = new AutoResetEvent(false);
Int32 actionThreadID = 0;
IDictionary<string, object> output = null;
WorkflowApplication workflowApp =
new WorkflowApplication(
new SayHello()
{
UserName = "Test"
});
// Create an Action<T> using a lambda expression
// To be invoked when the workflow completes
workflowApp.Completed = (e) =>
{
output = e.Outputs;
actionThreadID = Thread.CurrentThread.ManagedThreadId;
// Signal the test thread the workflow is done
sync.Set ();
};
workflowApp.Run ();
// Wait for the sync event for 1 second
sync.WaitOne (TimeSpan.FromSeconds(1));
Assert.IsNotNull (output,
"output not set, workflow may have timed out");
Assert.IsTrue (output.ContainsKey("WorkflowThread"),
"SayHello must contain an OutArgument named WorkflowThread");
// Don't know for sure what it is yet
var outarg = output["WorkflowThread"];
Assert.IsInstanceOfType (outarg, typeof(Int32),
"WorkflowThread must be of type Int32");
Assert.AreEqual (actionThreadID, (int)outarg,
"WorkflowThread should equal actionThreadID");
Debug.WriteLine("Test thread is " +
Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("Workflow thread is " + outarg.ToString());
}
- 上述代码实现
- AutoResetEvent实例来控制事件是否完成并通知调用程序
- 创建WorkflowApplication实例来启动工作流
- 指定Completed动作,这里使用了一个lambda表达式创建了一个委托,用于在工作流结束后接收输出参数字典以及使用一个本地变量来保存该委托运行时的线程ID
- 运行工作流程序实例
- 进行测试验证
- 输出参数是否为非空
- 是否存在WorkflowThread参数
- WorkflowThread参数是否Int32类型
- WorkflowThread参数值是否等于实际线程ID
- 注意:WorkflowApplication.Completed 和WorkflowApplication的其它属性是委托,而不是事件,因此如果要使用这些属性,需要提供一个方法、匿名方法或者lambda表达式
验证
- 按CTRL+SHIFT+B 编译
- 按CTRL+R, A 运行所有测试
- 验证所有测试应该能够通过
- 在Test Results窗口,双击ShouldReturnWorkflowThread 测试,看看测试Debug的消息是否与ShouldReturnWorkflowThreadInvoke 不同