实验目的:观察在工作流中使用InvokeMethod活动异步调用类方法时出现的情况。下载源代码
实验步骤:
1、创建一个用于显示工作流和主程序中各种消息的MessageLog类。
MessageLog类继承自System.Windows.Controls.RichTextBox控件,主要目的是为了能够以不同颜色和字体大小显示来自工作流和主程序的各种消息。
MessageLog类主要有以下四个公共方法:
AddLine(string text, Brush textColor) -- 添加一行指定文字颜色,默认文字大小的文本信息;
AddLine(string text, Brush textColor, double fontSize) -- 添加一行指定文字颜色和文字大小的文本信息;
BeginAddLine(string text, Brush textColor) -- 异步添加添加一行指定文字颜色,默认文字大小的文本信息;
BeginAddLine(string text, Brush textColor, double fontSize) -- 异步添加一行指定文字颜色和文字大小的文本信息;
BeginAddLine实际上是实现了跨线程更新用户界面的方法。
MessageLog类的源代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Threading;
namespace JasonLab.WPF.Controls
{
public class MessageLog:RichTextBox
{
private delegate void AddLineDelegate(string text,Brush foreColor,double fontSize);
private Delegate invokeDelegate = null;
private double fontSize=12;
private Paragraph paragraph = null;
public MessageLog():base()
{
FlowDocument doc = this.Document;
doc.Blocks.Clear();
paragraph = new Paragraph();
doc.Blocks.Add(paragraph);
invokeDelegate = new AddLineDelegate(AddLine);
fontSize = this.FontSize;
}
public void BeginAddLine(string text, Brush textColor)
{
this.Dispatcher.BeginInvoke(invokeDelegate, DispatcherPriority.Normal, text,textColor,fontSize);
}
public void BeginAddLine(string text, Brush textColor, double fontSize)
{
this.Dispatcher.BeginInvoke(invokeDelegate, DispatcherPriority.Normal, text, textColor, fontSize);
}
public void AddLine(string text, Brush textColor)
{
AddLine(text, textColor, fontSize);
}
public void AddLine(string text, Brush textColor, double fontSize)
{
Run r = new Run(text+"\r\n");
r.FontSize = fontSize;
r.Foreground = textColor;
paragraph.Inlines.Add(r);
this.ScrollToEnd();
}
}
}
2、创建一个等待用户输入的活动WaitUserInput。WaitUserInput类继承自System.Activities.NativeActivity。该活动的目的是将用户的控制指令传送给工作流。WaitUserInput类的源代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
namespace JasonLab.Workflow.Activities
{
public class WaitUserInput : NativeActivity<Boolean>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
private BookmarkCallback callback = null;
public WaitUserInput()
{
callback = new BookmarkCallback(OnComplete);
this.BookmarkName = "WaitUserInput";
}
protected override bool CanInduceIdle
{
get
{
return true;
}
}
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(this.BookmarkName.Get(context), callback);
}
void OnComplete(NativeActivityContext context, Bookmark bookmark, object state)
{
bool input = (bool)state;
context.SetValue(this.Result, input);
}
}
}
3、创建一个用于延时的类TestDelay。该类只有一个公共方法Delay(int milliSeconds),用于延时若干毫秒。请注意BeginDelay、EndDelay的实现方法,这种实现方法才能使InvokeMethod活动使用异步调用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace JasonLab.Components
{
public class TestDelay
{
private AsyncCallback callback;
private TestAsyncResult asyncResult;
public event EventHandler<DelayEventArgs> DelayEvent;
private void RaiseEvent(string message)
{
DelayEventArgs e = new DelayEventArgs();
e.Message = message;
if(DelayEvent!=null)
DelayEvent(this,e);
}
public void Delay(int milliSeconds)
{
RaiseEvent(string.Format("{0}同步调用延时{1}毫秒指令",DateTime.Now,milliSeconds));
Thread.Sleep(milliSeconds);
RaiseEvent(string.Format("{0}同步调用延时{1}毫秒结束", DateTime.Now,milliSeconds));
}
public IAsyncResult BeginDelay(int milliSeconds, AsyncCallback callback, object asyncState)
{
this.callback = callback;
TestAsyncResult result=new TestAsyncResult();
result.AsyncState = asyncState;
result.MilliSeconds=milliSeconds;
this.asyncResult = result;
System.Threading.ParameterizedThreadStart pts=new ParameterizedThreadStart(ProcessThread);
Thread t = new Thread(pts);
t.Start(milliSeconds);
RaiseEvent(string.Format("{0}开始异步调用延时{1}毫秒指令", DateTime.Now, milliSeconds));
return this.asyncResult;
}
private void ProcessThread(object param)
{
this.callback(this.asyncResult);
int milliSeconds = (int)param;
Thread.Sleep(milliSeconds);
RaiseEvent(string.Format("{0}异步调用延时{1}指令结束", DateTime.Now,milliSeconds));
}
public void EndDelay(IAsyncResult r)
{
TestAsyncResult result = r as TestAsyncResult;
}
internal class TestAsyncResult : IAsyncResult
{
public object AsyncState{get;set;}
public WaitHandle AsyncWaitHandle
{
get { throw new NotImplementedException(); }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return true; }
}
public int MilliSeconds { get; set; }
}
}
public class DelayEventArgs:EventArgs
{
public string Message{get;set;}
}
}
4、创建一个名为“PickWorkFlow"的活动。这是一个标准的,可在开发环境下编辑的工作流活动。该活动共有三个输入参数:
messageLog -- 用于显示文本信息的MessageLog类的实例;
delay5 -- 用于延时5秒的TestDelay类的实例。在工作流中使用InvokMethod活动调用delay5的Delay方法时使用了异步调用;
delay3 -- 用于延时3秒的TestDelay类的实例。在工作流中使用InvokMethod活动调用delay3的Delay方法时使用了同步调用;
PickWorkFlow的XAML源代码如下:
<Activity mc:Ignorable="sap" x:Class="JasonLab.Workflow.Activities.PickWorkflow" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="clr-namespace:JasonLab.WPF.Controls" xmlns:local1="clr-namespace:JasonLab.Components" xmlns:local2="clr-namespace:JasonLab.Workflow.Activities" 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:s4="clr-namespace:System;assembly=System.ServiceModel" 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:sw="clr-namespace:System.Windows;assembly=PresentationCore" xmlns:sw1="clr-namespace:System.Windows;assembly=PresentationFramework" xmlns:sw2="clr-namespace:System.Windows;assembly=WindowsBase" xmlns:swc="clr-namespace:System.Windows.Controls;assembly=PresentationFramework" xmlns:swcp="clr-namespace:System.Windows.Controls.Primitives;assembly=PresentationFramework" xmlns:swm="clr-namespace:System.Windows.Markup;assembly=PresentationCore" xmlns:swm1="clr-namespace:System.Windows.Markup;assembly=System.Xaml" xmlns:swm2="clr-namespace:System.Windows.Markup;assembly=PresentationFramework" xmlns:swm3="clr-namespace:System.Windows.Markup;assembly=WindowsBase" xmlns:swm4="clr-namespace:System.Windows.Media;assembly=PresentationCore" xmlns:swm5="clr-namespace:System.Windows.Media;assembly=PresentationFramework" xmlns:swm6="clr-namespace:System.Windows.Media;assembly=WindowsBase" xmlns:swma="clr-namespace:System.Windows.Media.Animation;assembly=PresentationCore" xmlns:swma1="clr-namespace:System.Windows.Media.Animation;assembly=PresentationFramework" xmlns:swt="clr-namespace:System.Windows.Threading;assembly=WindowsBase" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name="log" Type="InArgument(local:MessageLog)" />
<x:Property Name="delay5" Type="InArgument(local1:TestDelay)" />
<x:Property Name="delay3" Type="InArgument(local1:TestDelay)" />
</x:Members>
<sap:VirtualizedContainerService.HintSize>654,676</sap:VirtualizedContainerService.HintSize>
<mva:VisualBasic.Settings>Assembly references and imported namespaces for internal implementation</mva:VisualBasic.Settings>
<Flowchart DisplayName="工作流测试" sad:XamlDebuggerXmlReader.FileName="D:\SZH\软件项目\JasonLab\项目\Library\Workflow\Activities\PickWorkflow.xaml" sap:VirtualizedContainerService.HintSize="614,636">
<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 300,142.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Flowchart.StartNode>
<FlowStep x:Name="__ReferenceID7">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,142.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,277.5 300,307.5 300,373.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod sap:VirtualizedContainerService.HintSize="225,135" MethodName="BeginAddLine">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local:MessageLog">[log]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">工作流开始运行</InArgument>
<InArgument x:TypeArguments="av:Brush">[Brushes.Brown]</InArgument>
</InvokeMethod>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID6">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">200,373.5</av:Point>
<av:Size x:Key="ShapeSize">200,53</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,426.5 300,456.5 420.5,456.5 420.5,318.836666666667 300,318.836666666667 300,373.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Pick DisplayName="主流程" sap:VirtualizedContainerService.HintSize="1442,932">
<PickBranch DisplayName="延时5秒" sap:VirtualizedContainerService.HintSize="644,886">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<PickBranch.Trigger>
<local2:WaitUserInput BookmarkName="WaitDelay5" DisplayName="等待用户发出延时指令" sap:VirtualizedContainerService.HintSize="614,100" />
</PickBranch.Trigger>
<Flowchart DisplayName="延时5秒" sap:VirtualizedContainerService.HintSize="614,664">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">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 300,122.5</av:PointCollection>
<x:Double x:Key="Height">627.5</x:Double>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Flowchart.StartNode>
<FlowStep x:Name="__ReferenceID1">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,122.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,257.5 300,287.5 300,304</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时开始" sap:VirtualizedContainerService.HintSize="225,135" MethodName="BeginAddLine">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local:MessageLog">[log]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">[DateTime.Now.ToString() + "延时5秒开始"]</InArgument>
<InArgument x:TypeArguments="av:Brush">[Brushes.Brown]</InArgument>
</InvokeMethod>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID0">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,304</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,439 300,469 300,492.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时" sap:VirtualizedContainerService.HintSize="225,135" MethodName="Delay" RunAsynchronously="True">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local1:TestDelay">[delay5]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:Int32">5000</InArgument>
</InvokeMethod>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID2">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,492.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">422,570.163333333333 422,600.163333333333 422,615.163333333333</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时结束" sap:VirtualizedContainerService.HintSize="225,135" MethodName="BeginAddLine">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local:MessageLog">[log]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">[DateTime.Now.ToString() + "延时5秒结束"]</InArgument>
<InArgument x:TypeArguments="av:Brush">[Brushes.Brown]</InArgument>
</InvokeMethod>
</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>
</PickBranch>
<PickBranch DisplayName="延时3秒" sap:VirtualizedContainerService.HintSize="644,886">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<PickBranch.Trigger>
<local2:WaitUserInput BookmarkName="WaitDelay3" DisplayName="等待用户发出延时指令" sap:VirtualizedContainerService.HintSize="614,100" />
</PickBranch.Trigger>
<Flowchart DisplayName="延时3秒" sap:VirtualizedContainerService.HintSize="614,644">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">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 300,122.5</av:PointCollection>
<x:Double x:Key="Height">607.5</x:Double>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Flowchart.StartNode>
<FlowStep x:Name="__ReferenceID4">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,122.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,257.5 300,282.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时3秒开始" sap:VirtualizedContainerService.HintSize="225,135" MethodName="BeginAddLine" RunAsynchronously="True">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local:MessageLog">[log]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">[DateTime.Now.ToString() + "延时开始"]</InArgument>
<InArgument x:TypeArguments="av:Brush">[Brushes.Green]</InArgument>
</InvokeMethod>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID3">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,282.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,417.5 300,442.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时" sap:VirtualizedContainerService.HintSize="225,135" MethodName="Delay">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local1:TestDelay">[delay3]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:Int32">3000</InArgument>
</InvokeMethod>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID5">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">187.5,442.5</av:Point>
<av:Size x:Key="ShapeSize">225,135</av:Size>
<av:PointCollection x:Key="ConnectorLocation">422,570.163333333333 422,600.163333333333 422,615.163333333333</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<InvokeMethod DisplayName="延时3秒结束" sap:VirtualizedContainerService.HintSize="225,135" MethodName="BeginAddLine">
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="local:MessageLog">[log]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">[DateTime.Now.ToString() + "延时3秒结束"]</InArgument>
<InArgument x:TypeArguments="av:Brush">[Brushes.Green]</InArgument>
</InvokeMethod>
</FlowStep>
</FlowStep.Next>
</FlowStep>
</FlowStep.Next>
</FlowStep>
</Flowchart.StartNode>
<x:Reference>__ReferenceID3</x:Reference>
<x:Reference>__ReferenceID4</x:Reference>
<x:Reference>__ReferenceID5</x:Reference>
</Flowchart>
</PickBranch>
</Pick>
<FlowStep.Next>
<x:Reference>__ReferenceID6</x:Reference>
</FlowStep.Next>
</FlowStep>
</FlowStep.Next>
</FlowStep>
</Flowchart.StartNode>
<x:Reference>__ReferenceID7</x:Reference>
<x:Reference>__ReferenceID6</x:Reference>
</Flowchart>
</Activity>
下图是PickWorkFlow主流程的截图。
5、创建一个名为"TestWorkFlow"的WPF窗体项目,用于加载工作流并显示各种消息。下面的源代码演示了如何加载工作流并向工作流传递参数
private void StartWorkflow()
{
Dictionary<string,object> param=new Dictionary<string,object>();
param.Add("log",messageLog);
param.Add("delay5",delay5);
param.Add("delay3", delay3);
delay3.DelayEvent += new EventHandler<DelayEventArgs>(OnDelayEvent);
delay5.DelayEvent += new EventHandler<DelayEventArgs>(OnDelayEvent);
string file = AppDomain.CurrentDomain.BaseDirectory + "Workflow.xaml";
try
{
PickWorkflow wf = new PickWorkflow();
application = new WorkflowApplication(wf, param);
application.Run();
}
catch (Exception e)
{
messageLog.AddLine(e.Message, Brushes.Red);
}
}
6、点击“延时5秒(异步)”和“延时3秒(同步)”按钮,观察消息记录框的输出结果。下图是先点击“延时5秒(异步)”按钮,然后快速点击“延时3秒(同步)”按钮后的运行结果。
实验结论:使用异步调用方法可以将需要占用大量CPU时间的类方法置于后台运行,从而使Pick活动不会因某一PickBranch分支未运行完而忽略掉其它PickBranch的执行。常见的问题是当某一PickBranch在执行时,用户按下界面按钮而发出的指令(如退出工作流)会被工作流忽略。