はじめに
過去に Java や VB.Net、C# 等に触れたことがあるのですが、それらに共通する TryCatch 周りの挙動と UiPath の挙動があまりに違っていたことに衝撃を受けたので、そのあたりをまとめてみました。
気づいたきっかけは @masatomix 様の以下 Qiita 記事です。(いつも参考にさせていただいております)
トライキャッチアクティビティが、Finally を実行しないケースがある…ですと…!?
というわけで VB.Net と UiPath でまったく同じソースを作成し、挙動の違い等を検証してみました。
検証環境
-
UiPath
Studio Pro 2021.6.0-beta.4514(Community License) -
使用パッケージ
UiPath.System.Activities v21.4.1
Finally が実行されないパターン
- Exception() 発生
- Catch節でログ出力
- Catch節で再スロー
- Finally節でログ出力
という動作パターンで比較してみます。
VB.Net で実行
Module Program
Sub Main(args As String())
Call Sample()
End Sub
Sub Sample()
Try
Throw New Exception() ' 例外発生
Catch ex As Exception
Console.WriteLine("Catch:Exception")
Throw ' 再スロー
Finally
Console.WriteLine("Finally")
End Try
End Sub
End Module
Catch:Exception
Unhandled exception. System.Exception: Exception of type 'System.Exception' was thrown.
Finally
VB.Net では、Catchブロック内での再スロー後に Finally が実行されていることを確認できました。
UiPath で実行
[Info] TEST の実行が開始されました。
[Info] Catch:Exception
[Error] スロー: 種類 'System.Exception' の例外がスローされました。
[Info] TEST の実行が終了しました。 in: 00:00:00
まったく同じソースを UiPath で再現したところ、なんと** Finally が実行されていません!**
VB.Net とは違う動きをしました。
再スローアクティビティは、このあたりを抑えて使用しないと危険ですね。
Finally は、Tryブロック、もしくはCatchブロックを最後まで実行したときのみ、正常に呼ばれる。
再スローアクティビティの対策
では再スローアクティビティは基本使用しないほうがいいのでしょうか?
そうではなく、個人的には上記仕様を理解した上でなら効果的に使えると考えています。
また以下の手法で、再スローアクティビティを使用しつつ Finally も正常に実行できるよう対策可能です。
- Catch節での再スローを、さらに上位の TryCatch で拾う
先ほど UiPath サンプルで使用したトライキャッチ自体を、さらに外側のトライキャッチで囲んでみました。
実行結果は以下です。
[Info] TEST の実行が開始されました。
[Info] Catch:Exception
[Debug] 再スロー
[Info] Finally
[Info] 【外側】Finally
[Info] TEST の実行が終了しました。 in: 00:00:00
今度は再スロー後の Finally が実行されました!
どうやら UiPath は以下の仕様のようです。
再スローアクティビティがスローした例外を捕捉する機構が備わっていれば、Finally が正常に実行される。
よって外側をもう一つトライキャッチで囲わなくても、実は グローバル例外ハンドラー を設定するだけで再スロー後に Finally が実行されることも確認できました。
グローバル例外ハンドラー の有無だけで Finally 実行有無が変わる、というのは Studio 上でワークフローをパッと見ただけではなかなか気づけないポイントなので注意が必要です。
Catch節の定義順に伴う到達箇所について
検証していくうちに、Finally 実行有無とは別の違いも見えてきたのでまとめます。
検証:Catch節の定義順によって、VB.Net と UiPath で動作差異がある
VB.Net で実行
Module Program
Sub Main(args As String())
Call Sample()
End Sub
Sub Sample()
Try
Throw New ArgumentNullException() ' 例外スロー
Catch ex As Exception
Console.WriteLine("Catch:Exception")
Catch ex As SystemException
Console.WriteLine("Catch SystemException")
Catch ex As ArgumentException
Console.WriteLine("Catch:ArgumentException")
Catch ex As ArgumentNullException
Console.WriteLine("Catch:ArgumentNullException")
End Try
End Sub
End Module
実行前からコンパイラが警告を出している糞コードですが、検証用のためご容赦下さい。
VB.Net、C#、Java すべてに共通して言えますが、Catch節の定義順というのは意味があります。各例外クラスが親子関係にある場合、親クラスに該当する Catch ブロックが存在するとそこで捕捉されます。
上記例の場合、全ての例外クラスの親クラスである Exception
が Catch 節で最初に定義されているため、どんな例外が発生しても Catch ex As Exception
で捕捉されてしまい、それ以外の Catch ブロックに到達することはありません。(だからコンパイラが警告を出している)
もちろん実行結果は以下の通りです。
Catch:Exception
UiPath で実行
[Info] TEST の実行が開始されました。
[Info] Catch:ArgumentNullException
[Info] TEST の実行が終了しました。 in: 00:00:01
なんと Exception ではなく、ArgumentNullException に到達しました!
ここも VB.Net とは違う動きをするようです。
UiPath のトライキャッチでは、Catch 節の定義順に関係なく、スローされた例外と一致する Catch 節で捕捉される
最後に
トライキャッチ周りは UiPath 独特の動きが多いなー、という印象を持ちました。
次回はUiPathを使ったAPI連携の記事を予定しています。