トップ回答者
try~catchでリトライさせた中でのcatch

質問
-
毎々お世話になっております。
WindowsXP-Pro Ver.2002 SP2 上で
Visual Studio 2005 Enterprise Edition でVB2005による開発を行っております。現在XML Webサービスを用いたリッチクライアント環境下で動作するアプリを開発しております。
WebサービスにDBへの書き込みを依頼する際に、ソケット関連のエラーやタイムアウトでエラー終了してしまうのを防ぐ為に下記のようにコーディングしました。
(1)ソケット関連またはタイムアウトが発生したらメッセージを表示(frmMesssageBoxを使用)させ、その間にWebサービスへの依頼をリトライする。
(2)リトライ後再度ソケット関連またはタイムアウトが発生したら再リトライするか否かをオペレータに委ねる。
(3)連続して3回リトライしたら、諦めてエラー終了する。
Dim strErrorMessage As String = ""
Dim rs As Boolean
Dim ReTryCnt As Integer = 0
Dim frmMessageBox As frmMessageBox = NothingTry
ReTry:
rs = _wmm.SetRecords(strSQLEx, strTableName, strExecute, strErrorMessage, oDS) ’WebサービスにDB書き込み依頼
Catch ex As System.Net.Sockets.SocketException
ReTryCnt = ReTryCnt + 1
Select Case ReTryCnt
Case 1
frmMessageBox = New frmMessageBox("只今回線が大変込み合っております。しばらくお待ちください。")
frmMessageBox.Show() : Application.DoEvents()
Case Is > 3
MessageBox.Show("ソケット関連のエラーが発生しました。", "データベース書き込み", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Return False
Case Else
If MessageBox.Show("ソケット関連のエラーが発生しました。" & vbCrLf & "リトライ:" & ReTryCnt.ToString & "回目", "データベース書き込み", MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation) = DialogResult.Cancel Then
Exit Try
End If
End Select
GoTo ReTry
Catch ex2 As TimeoutException
ReTryCnt = ReTryCnt + 1
Select Case ReTryCnt
Case 1
frmMessageBox = New frmMessageBox("只今回線が大変込み合っております。もう暫らくお待ちください。")
frmMessageBox.Show() : Application.DoEvents()
Case Is > 3
MessageBox.Show("タイムアウトが発生しました。", "データベース書き込み", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Return False
Case Else
If MessageBox.Show("タイムアウトが発生しました。" & vbCrLf & "リトライ:" & ReTryCnt.ToString & "回目", "データベース書き込み", MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation) = DialogResult.Cancel Then
Exit Try
End If
End Select
GoTo ReTry
Finally
If Not frmMessageBox Is Nothing Then
frmMessageBox.Dispose()
End If
End Try1回目は期待通りリトライするのですが、2回目はシステムエラーとなってしまいます。
Try~Catchの中でリトライを行なった場合、2度目のCatch は効かないのでしょうか?
よろしくお願いします。
回答
-
「システムエラー」が何かが気になるところですが、根本的な問題として可能であればGoToは使わない方が良いと思います。見通しが悪くなりますし、不適切なところに戻せばおかしな動作をすると思います。GoToを使わず、書かれている処理を一つのプロシージャにまとめ、そのプロシージャを例外が発生したら3回まで呼ぶなどの構造にした方が良いと思います。
#当てずっぽですが、GoToで戻すならTryの前でしょうが、これでうまく動いたとしても上記の理由であまりお勧めしません。
#GoToやガード句の問題については過去にいろいろ論議されていますので、ご存じなければ一度調べた上でポリシーを決められると良いと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
外池です。VBではなくC#の話なので、ここでは、あくまで参考として。
C#の言語仕様では、GoToは、ブロックの中同士ならOK、ブロックの中から外へはOK、ブロックの外から中へはNGが原則。唯一の例外が、Try-Catch-FinallyのFinallyブロックで、Finallyブロックの中中同士のみOK、中から外へはNG,ってことだと思います。
なので・・・、
TryブロックからTry-Cath-Finallyの外へ出るのはOK(Finallyブロックは実行される。)、
CatchブロックからTry-Catch-Finallyn外へ出るのはOK(Finallyブロックは実行される。)、
CatchブロックからTryブロックへは、NG(明示はされていませんが)
(ホームページを再開しました)- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
VB での検証ありがとうございます。C# ではブランケットがブロックを決め、ラベルの有効範囲はブロックなので、try {} catch {} で別ブロックとなるためにラベルが参照できない、ということですね。VB では、ブロックがメソッド全域になるため、Try ~ Catch ~ で同じブロックにあり、ラベルが参照できるんですね。
しかし、1回目の Finally で Dispose された frmMessageBox を使っているのかな~と思ったのですが、2回目以降は MessageBox を使っていますね。Dispose を複数回実行することは、それがあることを前提に実装することが勧められていますから、そのように実装されていることでしょう。なので、このあたりで例外が発生する要因は、無い様に思う。ますます「システムエラー」がなんなのか、気になる。。。
Jitta@わんくま同盟- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
なのでロジックがおかしかろうと「「システムエラー」とは、どのようなエラーが発生しているのでしょうか? もっと具体的に記述してください。」というコメントになるんですが…。
「try~catchでリトライさせた中でのcatch」というタイトルなのでロジックに関する質問のように捉えがちですが、原因はそこではないと考えています。ロジックが何であれ、Webサービスでエラーが発生した後、リトライの仕方次第では再エラーになるのは当然でしょう。
# Webサービスへ再接続が必要とかそういう問題じゃないかな、というのが最初からの予想。- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
すべての返信
-
「システムエラー」が何かが気になるところですが、根本的な問題として可能であればGoToは使わない方が良いと思います。見通しが悪くなりますし、不適切なところに戻せばおかしな動作をすると思います。GoToを使わず、書かれている処理を一つのプロシージャにまとめ、そのプロシージャを例外が発生したら3回まで呼ぶなどの構造にした方が良いと思います。
#当てずっぽですが、GoToで戻すならTryの前でしょうが、これでうまく動いたとしても上記の理由であまりお勧めしません。
#GoToやガード句の問題については過去にいろいろ論議されていますので、ご存じなければ一度調べた上でポリシーを決められると良いと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
外池と申します。
Try
ReTry:
Catch
GoTo ReTry
Finally
End Try
こういう制御がアリなのか? を、検証されてはいかがでしょう? ただ、私の第一印象としては「気持ち悪くてやりたくない」です。(GoTo文がコンパイルを通るのだから・・・、アリなのかなぁ。)再試行するループの入り口
Try
試行する動作
Catch
例外が発生した「状況」の整理
(何か変数に「状況」を示すデータを入れておく)
Finally
End Try
If (上述の変数を調べて)再試行すべき状況 Then ループ Else 試行打ち切りという構造にされては?
(ホームページを再開しました) -
Try
ReTry:
Catch
GoTo ReTry
Finally
End Try
こういう制御がアリなのか? を、検証されてはいかがでしょう? ただ、私の第一印象としては「気持ち悪くてやりたくない」です。(GoTo文がコンパイルを通るのだから・・・、アリなのかなぁ。)
ありかなしか・・・言語仕様上ありかなしかで言うと、ありです。→GoTo ステートメント私なら、try の中を別メソッドに切り出して、戻り値判定にするかなぁ。
編集追記:
C#はどうなんだ?仕様書には、finaly ブロックで呼ぶときは、同じ finaly ブロックの中に飛び先がなければならない、としかないなぁ。catch から try に入れるのかは書いてない、っていうことだけど。try または catch から 抜け出るときはかいてあるけど。あれ?finaly って、いつ実行される?catch から try に戻ったときに実行されたら、システムエラー(Dispose 済み例外)が出るだろうね。
さらに追記:
VB はインストールしていなかったので C# でやってみた。using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int index = 0; l1: if (index == 2) { goto l3; } try { ++index; Console.WriteLine(string.Format("TRY {0}", index)); if (index == 1 || index == 2) { throw new Exception(); } } catch (Exception) { Console.WriteLine(string.Format("CATCH {0}", index)); if (index == 1) { goto l1; } } finally { Console.WriteLine(string.Format("FINALLY {0}", index)); } if (index == 2) { goto l1; } l3: ; } } }
TRY 1 CATCH 1 FINALLY 1 TRY 2 CATCH 2 FINALLY 2 続行するには何かキーを押してください . . .
このように、コードは「上」に移動するけど、finally ブロックの実行はブロックを抜けるときなので、catch ブロックから try ブロック内のラベルに移動するときに finally が実行される。ただ、C# では try においたラベルに catch からジャンプすることができなかったので、VB とはちょっと違うかもしれない。
Jitta@わんくま同盟
- 編集済み Jitta 2011年10月26日 13:45
-
外池です。
Visual Basicで、Try-Catch-Finallyの中でどのようにGoTo文を使えるか、というと、アリなんですね。で、Catchの内側から、Tryの内側へもGoToできるということで、確かに、以下のコードは何の問題もなく動きました。
この場合、例外をThrowした後Retryするときに、Finallyは働きません。Finallyが働くのは、例外をThrowしないときか、例外をThrowするけどRetryしないときだけです。Try-Catch-Finallyの構造から抜けるときにFinallyが動くという理解に合致しています。
Module Module1 Sub Main() Console.WriteLine("Test of GoTo in a Try-Catch-Finally clause") Dim s As String Try LoopPoint: Console.WriteLine("Throw an exception? ") s = Console.ReadLine If s = "yes" Then Throw New Exception Catch ex As Exception Console.WriteLine("The exception thrown and caught") Console.WriteLine("Retry? ") s = Console.ReadLine If s = "yes" Then GoTo LoopPoint Finally Console.WriteLine("Entered into the Finally clause") End Try Console.WriteLine("Exited from the Try-Catch-Finally clause") s = Console.ReadLine End Sub End Module
LoopPoint:をTryの上に置けば、Retryの時にもFinallyは動きます。これも、これで、Finallyの動き方としては、納得はできます。
ご質問のコードのうち、frmMessageBoxのDisposeの有無がシステムエラーに繋がるかと言えば・・・、frmMessageBoxの新しいインスタンスを作るのは、初回試行に例外が発生したときのみのようなので、システムエラーとは無関係ですね。
結局は、システムエラーの内容によりけり・・・、ですか。
(ホームページを再開しました)- 編集済み 外池 2011年10月27日 1:57
-
外池です。VBではなくC#の話なので、ここでは、あくまで参考として。
C#の言語仕様では、GoToは、ブロックの中同士ならOK、ブロックの中から外へはOK、ブロックの外から中へはNGが原則。唯一の例外が、Try-Catch-FinallyのFinallyブロックで、Finallyブロックの中中同士のみOK、中から外へはNG,ってことだと思います。
なので・・・、
TryブロックからTry-Cath-Finallyの外へ出るのはOK(Finallyブロックは実行される。)、
CatchブロックからTry-Catch-Finallyn外へ出るのはOK(Finallyブロックは実行される。)、
CatchブロックからTryブロックへは、NG(明示はされていませんが)
(ホームページを再開しました)- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
VB での検証ありがとうございます。C# ではブランケットがブロックを決め、ラベルの有効範囲はブロックなので、try {} catch {} で別ブロックとなるためにラベルが参照できない、ということですね。VB では、ブロックがメソッド全域になるため、Try ~ Catch ~ で同じブロックにあり、ラベルが参照できるんですね。
しかし、1回目の Finally で Dispose された frmMessageBox を使っているのかな~と思ったのですが、2回目以降は MessageBox を使っていますね。Dispose を複数回実行することは、それがあることを前提に実装することが勧められていますから、そのように実装されていることでしょう。なので、このあたりで例外が発生する要因は、無い様に思う。ますます「システムエラー」がなんなのか、気になる。。。
Jitta@わんくま同盟- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56
-
なのでロジックがおかしかろうと「「システムエラー」とは、どのようなエラーが発生しているのでしょうか? もっと具体的に記述してください。」というコメントになるんですが…。
「try~catchでリトライさせた中でのcatch」というタイトルなのでロジックに関する質問のように捉えがちですが、原因はそこではないと考えています。ロジックが何であれ、Webサービスでエラーが発生した後、リトライの仕方次第では再エラーになるのは当然でしょう。
# Webサービスへ再接続が必要とかそういう問題じゃないかな、というのが最初からの予想。- 回答としてマーク へっぽこドラマー 2011年11月7日 0:56