2011年11月15日火曜日

ワークフロー上のアクティビティを非同期に複数動作させるには

原則として 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 アクティビティを利用するようにし、結果を比較してみます。

exec1exec2

このように最初の Parallel アクティビティでの実行は不定ですが、後半の Parallel アクティビティの実行は常に一定となっているのがわかると思います。このような方法でマルチスレッド化したワークフローを実行する事が可能になります。

0 件のコメント:

コメントを投稿