在微软WF4中有一个Parallel活动,它可以模拟并行执行,但可惜的是Parallel活动并不是一个真正意义上的并行计算方案,实际上Parallel利用了一个线程去分时间段执行下面的各个分支。
今天,我们要做的就是基于Parallel活动和微软.net framework 4中的并行任务Task,构造一个实际的并行工作流。并将它扩展为通用的并行工作流解决方案。
我们首先构造一个虚拟的订单处理流程,它包含主要三个活动
(1) 订单初始化
(2) 将订单有关信息拷贝到零件数据库(Part DB)
(3) 将订单有关信息拷贝到车辆数据库(Vehicle DB)
(4) 结束订单
传统的做法是构造一个服务,顺序执行(1)->(2)->(3),但在实际业务过程中,一旦订单初始化后,2,3流程没有先后次序关系,可以并发执行。
来看业务流程图 CloseOrder Workflow (FlowChart):
首先我们定义一个简单的Order类
public class Order
{
public Guid Id { get ; set ;}
public string Customer { get ; set ; }
public string PartNumber { get ; set ; }
public int Quantity { get ; set ; }
public OrderStatus Status { get ; set ; }
}
public enum OrderStatus
{
Initilized,
InProcess,
Cancelled,
Failed,
Succeeded
}
然后分别模拟零件数据库同步活动和车辆数据库同步活动。
public class PartDBReplicationActivity : NativeActivity
{
public InOutArgument < Order > OrderInActivity { get ; set ; }
protected override void Execute(NativeActivityContext context)
{
// Console.WriteLine("PartDBReplication Activity starts at:{0}",DateTime.Now.ToString());
Order order = context.GetValue(OrderInActivity);
order.Status = OrderStatus.InProcess;
// Call Part service
Thread.Sleep( 5000 );
// Console.WriteLine("PartDBReplication Activity ends at {0}.\r\n", DateTime.Now.ToString());
}
}
public class VehicleDBReplicationActivity : NativeActivity
{
public InOutArgument < Order > OrderInActivity { get ; set ; }
protected override void Execute(NativeActivityContext context)
{
// Console.WriteLine("VehicleDBReplication Activity starts at:{0}", DateTime.Now.ToString());
Order order = context.GetValue(OrderInActivity);
order.Status = OrderStatus.InProcess;
// Call Vehicle service
Thread.Sleep( 5000 );
// Console.WriteLine("VehicleDBReplication Activity ends at {0}.\r\n", DateTime.Now.ToString());
}
}
两个活动分别要执行5秒钟(Thread.Sleep(5000)),但是由于并行,它们实际应该总共只要花5秒。
再来看订单初始化活动
public class OpenOrderActivity : NativeActivity
{
public InOutArgument < Order > OrderInActivity { get ; set ; }
protected override void Execute(NativeActivityContext context)
{
Order order = new Order();
order.Id = Guid.NewGuid();
order.PartNumber = " 10506 " ;
order.Customer = " CustomerA " ;
order.Quantity = 1 ;
order.Status = OrderStatus.Initilized;
context.SetValue(OrderInActivity, order);
}
}
结束订单
public class CloseOrderActivity : NativeActivity
{
public InOutArgument < Order > OrderInActivity { get ; set ; }
protected override void Execute(NativeActivityContext context)
{
// Console.WriteLine("CloseOrder Activity starts at:{0}", DateTime.Now.ToString());
Order order = context.GetValue(OrderInActivity);
order.Status = OrderStatus.Succeeded;
// Console.WriteLine("CloseOrder Activity ends at {0}.", DateTime.Now.ToString());
// Console.WriteLine("");
}
}
添加我们定制的活动到主工作流活动前请先编译项目,新的定制活动就会出现在Toolbox 的最上面。 我们运行一下整个流程
class Program
{
static void Main( string [] args)
{
DateTime dtStart = DateTime.Now;
Console.WriteLine( " CloseOrder workflow starts at:{0} " , dtStart);
Console.WriteLine( " -------------------------------------------------------------- " );
WorkflowInvoker.Invoke( new CloseOrderWorkflow());
// WorkflowInvoker.Invoke(new CloseOrderParallelWorkflow());
Console.WriteLine( " -------------------------------------------------------------- " );
DateTime dtEnd = DateTime.Now;
Console.WriteLine( " CloseOrder workflow ends at:{0} " , dtEnd.ToString());
Console.WriteLine( " Total time elapsed in seconds :{0} " ,( dtEnd - dtStart).Seconds.ToString());
Console.Read();
}
}
看看运行结果,
奇怪,整个活动花了10秒,Parallel活动的分支不是真正意义上的并行,主要原因我们刚才说过工作流中的Parallel利用了一个线程模拟分支并行的,我们的Thread.Sleep阻塞了其他分支的运行,导致整个流程运行实际时间等于串行执行这四个活动。
下一节我们将要利用.net framework4里面的Parallel Task去实现真正意义的并行工作流。
P.S. CloseOrderWorkflow.xaml 源代码 (项目命名空间为ParallelTask)
< Activity mc:Ignorable ="sap" x:Class ="ParallelTask.CloseOrderWorkflow" sap:VirtualizedContainerService.HintSize ="654,676" mva:VisualBasic.Settings ="Assembly references and imported namespaces for internal implementation" xmlns ="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:av ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local ="clr-namespace:ParallelTask" xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv ="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva ="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s ="clr-namespace:System;assembly=mscorlib" xmlns:s1 ="clr-namespace:System;assembly=System" xmlns:s2 ="clr-namespace:System;assembly=System.Xml" xmlns:s3 ="clr-namespace:System;assembly=System.Core" xmlns:sa ="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad ="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap ="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg ="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1 ="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2 ="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3 ="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd ="clr-namespace:System.Data;assembly=System.Data" xmlns:sl ="clr-namespace:System.Linq;assembly=System.Core" xmlns:st ="clr-namespace:System.Text;assembly=mscorlib" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" >
< Flowchart sad:XamlDebuggerXmlReader.FileName ="C:\Project\Labs\Parallel Execution\ParallelTask\ParallelTask\CloseOrderWorkflow.xaml" sap:VirtualizedContainerService.HintSize ="614,636" >
< Flowchart.Variables >
< Variable x:TypeArguments ="local:Order" Name ="OrderInProcess" />
</ Flowchart.Variables >
< sap:WorkflowViewStateService.ViewState >
< scg3:Dictionary x:TypeArguments ="x:String, x:Object" >
< x:Boolean x:Key ="IsExpanded" > False </ x:Boolean >
< av:Point x:Key ="ShapeLocation" > 270,2.5 </ av:Point >
< av:Size x:Key ="ShapeSize" > 60,75 </ av:Size >
< av:PointCollection x:Key ="ConnectorLocation" > 300,77.5 300,107.5 290,107.5 290,119 </ av:PointCollection >
</ scg3:Dictionary >
</ sap:WorkflowViewStateService.ViewState >
< Flowchart.StartNode >
< FlowStep x:Name ="__ReferenceID0" >
< sap:WorkflowViewStateService.ViewState >
< scg3:Dictionary x:TypeArguments ="x:String, x:Object" >
< av:Point x:Key ="ShapeLocation" > 190,119 </ av:Point >
< av:Size x:Key ="ShapeSize" > 200,22 </ av:Size >
< av:PointCollection x:Key ="ConnectorLocation" > 290,141 290,171 290,175.5 </ av:PointCollection >
</ scg3:Dictionary >
</ sap:WorkflowViewStateService.ViewState >
< local:OpenOrderActivity sap:VirtualizedContainerService.HintSize ="200,22" OrderInActivity ="[OrderInProcess]" />
< FlowStep.Next >
< FlowStep x:Name ="__ReferenceID2" >
< sap:WorkflowViewStateService.ViewState >
< scg3:Dictionary x:TypeArguments ="x:String, x:Object" >
< av:Point x:Key ="ShapeLocation" > 190,175.5 </ av:Point >
< av:Size x:Key ="ShapeSize" > 200,49 </ av:Size >
< av:PointCollection x:Key ="ConnectorLocation" > 290,224.5 290,254.5 290,269 </ av:PointCollection >
</ scg3:Dictionary >
</ sap:WorkflowViewStateService.ViewState >
< Parallel sap:VirtualizedContainerService.HintSize ="554,86" >
< local:PartDBReplicationActivity sap:VirtualizedContainerService.HintSize ="200,40" OrderInActivity ="[OrderInProcess]" />
< local:VehicleDBReplicationActivity sap:VirtualizedContainerService.HintSize ="200,40" OrderInActivity ="[OrderInProcess]" />
</ Parallel >
< FlowStep.Next >
< FlowStep x:Name ="__ReferenceID1" >
< sap:WorkflowViewStateService.ViewState >
< scg3:Dictionary x:TypeArguments ="x:String, x:Object" >
< av:Point x:Key ="ShapeLocation" > 190,269 </ av:Point >
< av:Size x:Key ="ShapeSize" > 200,22 </ av:Size >
</ scg3:Dictionary >
</ sap:WorkflowViewStateService.ViewState >
< local:CloseOrderActivity sap:VirtualizedContainerService.HintSize ="200,22" OrderInActivity ="[OrderInProcess]" />
</ FlowStep >
</ FlowStep.Next >
</ FlowStep >
</ FlowStep.Next >
</ FlowStep >
</ Flowchart.StartNode >
< x:Reference > __ReferenceID0 </ x:Reference >
< x:Reference > __ReferenceID1 </ x:Reference >
< x:Reference > __ReferenceID2 </ x:Reference >
</ Flowchart >
</ Activity >