並列 (Parallel) アクティビティについて

about-parallel-activities

はじめに

並列 (Parallel) アクティビティをご存じでしょうか?

このアクティビティは、Studio のアクティビティパネル上では、[ ワークフロー>制御 ] のカテゴリーの下に配置されています。

その実体は、.NET Framework の System.Activities.Statements 名前空間に実装されている Parallel クラスです。

 

マニュアルページ

Microsoft 社のマニュアルページには、Parallel クラスは「すべての子アクティビティを同時に非同期で実行するアクティビティ。」という簡単な説明があります。これだけを読むとなんでもかんでも並列実行してくれそうな雰囲気です。が、実のところそうではありません。

「注釈」というパラグラフに次の説明が続きます。「 Activity オブジェクトは非同期で実行されますが、個別のスレッドでは実行されません。このため、連続する各アクティビティは、前にスケジュールされたアクティビティが完了するかアイドル状態になった場合にのみ実行されます。

いったいどういうことを意味するのでしょうか。

 

解説

並列アクティビティの中に配置された子アクティビティは、Studio のデザイナーパネル上、左から右に順番に実行がスケジュールされます。スケジュールされたアクティビティは順に実行されることになりますが、実行中のアクティビティが完了するかアイドル状態になった場合にのみ、次にスケジュールされたアクティビティが実行されます。したがって、次の図の場合、①→②→③の順にスケジュールされて実行されます。

about-parallel-activities0001

アクティビティの完了後、次のアクティビティが実行されるというのであれば、それは並列ではなく直列、つまり逐次実行です。シーケンス アクティビティと同じです。

シーケンス アクティビティは並列アクティビティと同様に子アクティビティの実行を順番にスケジュールしますが、1度にスケジュールする子アクティビティは1個だけです。そして、子アクティビティの実行完了時に次の子アクティビティをスケジュールすることで逐次実行を実現しています。それに対し、並列アクティビティは1度にすべての子アクティビティの実行をスケジュールする点が異なります。

ということは、並列アクティビティに並列処理をさせるための鍵は「アイドル状態」とは何かです。

 

アイドル状態とは

アクティビティの実行がアイドル状態になるとは、例えばブックマークの作成によってアクティビティの実行が一時停止する状態を指します。

ブックマークについては、Microsoft 社のマニュアルページが参考になります。

 

具体的には、NativeActivity クラスによるアクティビティの実装において、Execute メソッドの引数として渡された NativeActivityContext クラスのインスタンスに対し、CreateBookmark メソッドを呼び出すことよってブックマークを作成できます。その時点で、アクティビティの実行は一時停止します。そして、WorkflowApplication クラスによりワークフローが実行されている場合、同クラスの ResumeBookmark メソッドを呼び出して、指定したブックマークによって一時停止しているアクティビティの実行を再開することができます。

 

実際のところ

ところで、ブックマークについて理解できたとして、それを応用して並列アクティビティに並列処理をさせるワークフローを開発できるのでしょうか。難しいです。CodeActivity クラスや NativeActivity クラスにより実装されたアクティビティは、本体ロジックの実行がアクティビティの実行と同じスレッドで行われる同期型のアクティビティです。NativeActivity クラスは、ブックマークによりアイドル状態になって、実行制御をアクティビティ完了前に次の子アクティビティに移すことができますが、各アクティビティの本体ロジックが同じスレッドで実行される点で並列処理ではありません。ブックマークの取り扱いも、ワークフローを実行する主体(ワークフロー ランタイムを使う側)であれば行えますが、実行される側(ワークフロー ランタイムが実行する対象)のアクティビティとしては、ブックマークを受け渡す術がありませんし、WorkflowApplication クラスのインスタンスなども取れません。

ではどうすれば良いのでしょうか。

 

非同期アクティビティ

それは、AsyncCodeActivity クラスにより実装されたアクティビティを配置することです。AsyncCodeActivity クラスは、アクティビティの本体ロジックを別スレッドで実行する非同期型のアクティビティです。

 

AsyncCodeActivity クラスにより実装されたアクティビティの実行が開始されると、本体ロジックの実行が別スレッドで開始されます。アクティビティ自身は本体ロジックの実行完了までアイドル状態となり、次に実行がスケジュールされている(右側)の子アクティビティがただちに実行開始されます。この動作により、並列処理が行えます。

 

カスタムアクティビティによるテスト実行

サンプルコード(カスタムアクティビティ)を使って並列アクティビティの動きを見てみましょう。

UiPathTeam.ParallelTest.Activities パッケージは、CodeActivity クラスにより実装した「同期」アクティビティと AsyncCodeActivity クラスにより実装した「非同期」アクティビティを含みます。

サンプルコード:ParallelTest.zip

 

これらのアクティビティはいずれも、指定された文字列メッセージを System.Console.WriteLine メソッドを呼び出して指定した回数だけ繰り返し出力します。違いは、「同期」アクティビティが実行制御と同じスレッドで処理を行うのに対し、「非同期」アクティビティは別スレッドで処理を行うところです。

今回の例では、各パターンで各アクティビティに5回ずつ繰り返しメッセージを出力させています。

 

テストパターン1~同期→同期

まず「同期」アクティビティを2個並列アクティビティに配置してみます。

about-parallel-activities0002

これを実行すると、左側の「同期」アクティビティのメッセージ出力がすべて完了した後、右側の「同期」アクティビティのメッセージ出力が始まるのが分かります。

about-parallel-activities0003

テストパターン2 ~非同期→非同期

今度は「非同期」アクティビティを2個並列アクティビティに配置してみます。

about-parallel-activities0004

これを実行すると、左側の「非同期」アクティビティの1回目のメッセージ出力と右側の「非同期」アクティビティの1回目のメッセージ出力の順序が逆転していますが、実行開始の順序は左側が先で右側が後です。(プールされているスレッドの状態やCPUサイクルの割り当て状態によって実際の出力順序は前後します。)そのあとは、左側と右側のメッセージが交互に出力されて、並列処理されていることが良く分かります。

about-parallel-activities0005

テストパターン3~同期→非同期

次は、左側に「同期」アクティビティを配置し、右側に「非同期」アクティビティを配置してみましょう。

about-parallel-activities0006

これを実行すると、左側の「同期」アクティビティのメッセージ出力がすべて完了した後、右側の「非同期」アクティビティのメッセージ出力が始まるのが分かります。左側の「同期」アクティビティがアイドル状態にならないため、完了するまで実装制御が右側に移らなかったためです。

about-parallel-activities0007

テストパターン4~非同期→同期

最後に、左側に「非同期」アクティビティを配置し、右側に「同期」アクティビティを配置してみましょう。

about-parallel-activities0008

これを実行すると、左側の「非同期」アクティビティのメッセージ出力と右側の「同期」アクティビティのメッセージ出力が混ざります。左側の「非同期」アクティビティのメッセージ出力は並列アクティビティとは別のスレッドで行われ、右側の「同期」アクティビティのメッセージ出力は並列アクティビティと同じスレッドで行われるためです。

about-parallel-activities0009

使用例

AsyncCodeActivity クラスにより実装したカスタムアクティビティを利用すると並列アクティビティで並列処理が行えることが分かりましたが、既存のアクティビティを使って並列処理できないでしょうか。

 

非同期セーフなアクティビティ

UiPath Activities ガイドに非同期セーフ (Asynchronous-Safe) と明示されたアクティビティは残念ながらありません。したがって、既存のアクティビティで安全に並列処理できるものはないと考えるほうが正しいでしょう。

 

そうとは言え、実際にはいくつかのアクティビティは非同期セーフのような扱いができます。

例えば、UiPath.Web.Activities パッケージの HTTP 要求 (HTTP Request) アクティビティや SOAP 要求 (SOAP Request) アクティビティなどは、AsyncCodeActivity クラスを派生する形で実装されていますので、ウェブ API アクセスを複数行う場合に、並列化すると処理時間の短縮の可能性があります。

about-parallel-activities0010

保証はありませんが...

さらに言ってしまうと、筆者独自調べでクリック (Click) アクティビティは使えそうなことが分かっています。公に動作保証はできませんが、次のシチュエーションで便利に使えるかもしれません。

Parallelアクティビティの使用例.zip

 

データをファイルなどに保存する場合、既にファイルが存在していると『上書きしてもよろしいでしょうか?』といった確認ダイアログボックスが表示されることがあります。それに応答するロジックを実装するために並列アクティビティを使うわけです。

about-parallel-activities0011

左側のクリック アクティビティで『上書きしてもよろしいでしょうか?』ダイアログを処理させます。[エラー発生時に実行を継続] プロパティにTrueを設定することにより、上書きでない場合=確認ダイアログが表示されない場合に対応できます。

右側のシーケンス アクティビティで主処理を行います。シーケンス アクティビティは、NativeActivity クラスにより実装されています。したがって、その実行はブロックしますので、もし複数種類のダイアログに対応する場合は、それぞれのダイアログに対応するためのクリック アクティビティを左側に並べて、主処理のシーケンス アクティビティを最も右側に置くようにしてください。 

並列アクティビティの [条件] プロパティには、Boolean型変数 saved を指定します。初期値を False として、書き込み完了した後、True に値を変更し、並列アクティビティを終わるようにします。

なお、並列アクティビティを使わずに、上書き「はい」をクリック、書き込み完了「OK」をクリック、を逐次実行するように配置しても動きますが、ファイルがまだ存在しない場合に第1のクリック アクティビティがタイムアウトするまで待つことになります。並列アクティビティを使うことでファイルがまだ存在しない場合でも待ち時間なしで処理できます。

別 Windows プロセスの利用

CodeActivity クラスや NativeActivity クラスにより実装されたアクティビティを並列処理させる方法はないのでしょうか。 

実はあります。ワークフローファイルを呼び出し (Invoke Workflow File) アクティビティを使う方法です。「分離」プロパティをオンにすることで、別 Windows プロセスでワークフローファイルを実行することができますので、それ(ら)を左側に配置(先に実行されるように)することで並列処理をさせることができます。ただし、いくつかの点で注意が必要です。例えば、別 Windows プロセスでのワークフロー実行は、同じ Windows プロセスで実行する場合よりも自由度が狭くなる点です。データの同期に Windows プロセス間の通信が必要になってくるため、簡単に行う場合は、データの入出力は引数を通して行う方法に限られてしまいます。また、非同期スレッドで実行する場合に比較し、別 Windows プロセスでのワークフローファイルの呼び出しにはやや時間がかかるため、並列アクティビティの実行開始直後には並列処理の効果はあまり見られないという点もあります。

 

グラフィカル ユーザー インターフェースに関する注意点

グラフィカル ユーザー インターフェース (GUI) に関して注意点がいくつかあります。

まず、入力フォーカスはデスクトップセッションにおいていつ如何なる場合でもたかだか1か所にしか設定できないということです。同時に複数設定することはできません。この事実は、それぞれのデスクトップに物理的にも仮想的にもキーボード1個とマウス1個しか接続できないことを考えると良く理解できると思います。ですから、並列アクティビティを利用して、同時に2か所以上の UI 要素をクリックさせようとしてもうまくいきませし、同時に2か所以上の UI 要素に文字を入力させようとしてもうまくいきません。ウィンドウ操作も同時に複数か所に行おうとすると予期しない結果を招くでしょう。 

また、一般的に UI スレッドと呼ばれる GUI を処理するスレッドもアプリケーション(Windows プロセス)でたかだか一つであるという点です。ですから、同じアプリケーションの異なる UI 要素に対する同時操作は期待する結果とならない可能性があります。最悪の場合、内部リソースの競合状態が起って実行が止まってしまうかもしれません。そのほか、並列アクティビティを利用してロボットからメッセージボックスを複数個同時に表示させようとしても期待した結果を得られないことに注意してください。

 

Excel 関連アクティビティは NG

UiPath.Excel.Activities パッケージで提供されている Microsoft Excel ファイルを操作する関連アクティビティですが、Excel アプリケーションスコープ アクティビティは、NativeActivity クラスで実装されています。したがって、複数の Excel ファイルを対象として並列アクティビティを利用し、処理時間の短縮を図ろうとする試みは失敗します。また、Microsoft 社のブログによると、実際に処理を行うことになるMicrosoft Office の OLE サーバープログラムは、STA (Single-Threaded Apartment) モデルで COM オブジェクトを処理する旨の記載があります。Excel 関連アクティビティには AsyncCodeActivity クラスで実装されているアクティビティもありますが、たとえクライアント側で並列のロジックを組んだとしても、結局サーバー側で逐次処理を行うため、大局的な並列処理の効果は望めません。

 

おわりに

以上、カスタムアクティビティによるテスト実行では、四つのパターンで並列アクティビティの子アクティビティの処理の様子を見てきました。AsyncCodeActivity クラスにより実装されたアクティビティを子アクティビティとして左側に配置(先に実行されるように)すると並列処理ができることを確認できたと思います。

また、使用例では、クリック アクティビティを使った例や別 Windows プロセスの利用に関するヒントを示しました。

並列アクティビティを正しく理解して利用することができれば、自動化の幅が広がることでしょう。きっと。

Topics:

Activities
Avatar Placeholder Big
Hideaki Narita

Senior Product Management Specialist, UiPath