原則として Workflow Foundation は非同期にて動作しますが、俗に言うマルチスレッドにタスクを分配して動作するような挙動は行いません。Parallel アクティビティというのもありますが、これも内部ではシングルスレッドで動作しており、待ち状態が発生した際に制御を切り替えるというものになっています。
このあたりで色々考える人は多いのですが、海外の Blog ですばらしいサンプルを見つけたので VB に移植してみました。
基本の考え方はアクティビティ内部でスレッドを作成するなどして、別個に動作させようというものです。今回のサンプルで気に入ったのが、Task クラスを利用しての方法だったというのと、WorkflowInvoker を新たに作成して実行してしまおう、という半ば力技的な部分です。
その他にも変数の引き継ぎや、既存アクティビティを別ワークフローへコピーして動作させる方法など、見るべき点が多かったサンプルでした。
1: Public Class AsyncTaskActivity
2: Inherits AsyncCodeActivity
3: Implements IRegisterMetadata
4:
5: <Browsable(False)>
6: Public Property Body As New ActivityAction
7:
8: Protected Overrides Function BeginExecute(context As System.Activities.AsyncCodeActivityContext, callback As System.AsyncCallback, state As Object) As System.IAsyncResult
9: Dim act = Me.CreateDynamicActivity(context)
10: Dim inpArgAndVal = Me.GetArgumentsAndVariables(context)
11:
12: Dim asyncTask = task.Factory.StartNew(Function(ignore)
13: Return WorkflowInvoker.Invoke(act, inpArgAndVal)
14: End Function, state)
15:
16: asyncTask.ContinueWith(Sub(t)
17: callback(t)
18: End Sub)
19:
20: Return asyncTask
21: End Function
22:
23: Protected Overrides Sub EndExecute(context As System.Activities.AsyncCodeActivityContext, result As System.IAsyncResult)
24: End Sub
25:
26: Private Function GetArgumentsAndVariables(ByVal context As AsyncCodeActivityContext) As IDictionary(Of String, Object)
27: Dim result As New Dictionary(Of String, Object)
28:
29: For Each wfProp As PropertyDescriptor In context.DataContext.GetProperties
30: result.Add(wfProp.Name, wfProp.GetValue(context.DataContext))
31: Next
32: Return result
33: End Function
34:
35: Private Function CreateDynamicActivity(ByVal context As AsyncCodeActivityContext) As Activity
36: Dim result As New DynamicActivity
37: For Each wfProp As PropertyDescriptor In context.DataContext.GetProperties
38: Dim dynActProperty As New DynamicActivityProperty With {
39: .Name = wfProp.Name,
40: .Type = GetType(InArgument(Of )).MakeGenericType(wfProp.PropertyType)
41: }
42: dynActProperty.Value = Activator.CreateInstance(dynActProperty.Type)
43: result.Properties.Add(dynActProperty)
44: Next
45:
46: VisualBasic.SetSettings(result, VisualBasic.GetSettings(Me))
47: result.Implementation = Function() (Body.Handler)
48: Return result
49: End Function
50:
51: #Region "メタデータ"
52:
53: Public Sub Register() Implements System.Activities.Presentation.Metadata.IRegisterMetadata.Register
54: Dim metadata As New Core.Presentation.DesignerMetadata
55: metadata.Register()
56:
57: Dim builder As New AttributeTableBuilder
58: builder.AddCustomAttributes(GetType(AsyncTaskActivity),
59: New DesignerAttribute(GetType(AsyncTaskActivityDesigner))
60: )
61: MetadataStore.AddAttributeTable(builder.CreateTable)
62: End Sub
63:
64: #End Region
65:
66: End Class
アクティビティ本体はこのような感じになります。AsyncCodeActivity を継承し非同期動作させるようにした上で、BeginExecute メソッド内部にて別スレッドを作成、WorkflowInvoker を利用して非同期に動作させています。
1: <sap:ActivityDesigner x:Class="AsyncTaskActivityDesigner"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
5: xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
6:
7: <sap:WorkflowItemPresenter Margin="7"
8: Item="{Binding Path=ModelItem.Body.Handler, Mode=TwoWay}"
9: HintText="Drop Activity"/>
10:
11: </sap:ActivityDesigner>
アクティビティデザイナーは上記のように非常にシンプルです。コンテナとなる WorkflowItemPresenter を配置しただけとなっています。これらを利用して実際に次のようなワークフローを作成してみました。
最初に非同期で挙動させるための Parallel アクティビティ、その後に通常通り Parallel アクティビティを利用するようにし、結果を比較してみます。
このように最初の Parallel アクティビティでの実行は不定ですが、後半の Parallel アクティビティの実行は常に一定となっているのがわかると思います。このような方法でマルチスレッド化したワークフローを実行する事が可能になります。
0 件のコメント:
コメントを投稿