none
try~catchでリトライさせた中でのcatch RRS feed

  • 質問

  • 毎々お世話になっております。

    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 = Nothing

            Try
    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 Try

    1回目は期待通りリトライするのですが、2回目はシステムエラーとなってしまいます。

    Try~Catchの中でリトライを行なった場合、2度目のCatch は効かないのでしょうか?

    よろしくお願いします。

    2011年10月26日 6:30

回答

  • 「システムエラー」が何かが気になるところですが、根本的な問題として可能であればGoToは使わない方が良いと思います。見通しが悪くなりますし、不適切なところに戻せばおかしな動作をすると思います。GoToを使わず、書かれている処理を一つのプロシージャにまとめ、そのプロシージャを例外が発生したら3回まで呼ぶなどの構造にした方が良いと思います。

    #当てずっぽですが、GoToで戻すならTryの前でしょうが、これでうまく動いたとしても上記の理由であまりお勧めしません。
    #GoToやガード句の問題については過去にいろいろ論議されていますので、ご存じなければ一度調べた上でポリシーを決められると良いと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年10月26日 8:07
    モデレータ
  • 外池です。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年10月27日 2:36
  • VB での検証ありがとうございます。C# ではブランケットがブロックを決め、ラベルの有効範囲はブロックなので、try {} catch {} で別ブロックとなるためにラベルが参照できない、ということですね。VB では、ブロックがメソッド全域になるため、Try ~ Catch ~ で同じブロックにあり、ラベルが参照できるんですね。

    しかし、1回目の Finally で Dispose された frmMessageBox を使っているのかな~と思ったのですが、2回目以降は MessageBox を使っていますね。Dispose を複数回実行することは、それがあることを前提に実装することが勧められていますから、そのように実装されていることでしょう。なので、このあたりで例外が発生する要因は、無い様に思う。ますます「システムエラー」がなんなのか、気になる。。。


    Jitta@わんくま同盟
    2011年10月27日 15:48
  • なのでロジックがおかしかろうと「「システムエラー」とは、どのようなエラーが発生しているのでしょうか? もっと具体的に記述してください。」というコメントになるんですが…。

    「try~catchでリトライさせた中でのcatch」というタイトルなのでロジックに関する質問のように捉えがちですが、原因はそこではないと考えています。ロジックが何であれ、Webサービスでエラーが発生した後、リトライの仕方次第では再エラーになるのは当然でしょう。
    # Webサービスへ再接続が必要とかそういう問題じゃないかな、というのが最初からの予想。

    2011年10月28日 1:02

すべての返信

  • 「システムエラー」とは、どのようなエラーが発生しているのでしょうか? もっと具体的に記述してください。
    2011年10月26日 6:45
  • 「システムエラー」が何かが気になるところですが、根本的な問題として可能であればGoToは使わない方が良いと思います。見通しが悪くなりますし、不適切なところに戻せばおかしな動作をすると思います。GoToを使わず、書かれている処理を一つのプロシージャにまとめ、そのプロシージャを例外が発生したら3回まで呼ぶなどの構造にした方が良いと思います。

    #当てずっぽですが、GoToで戻すならTryの前でしょうが、これでうまく動いたとしても上記の理由であまりお勧めしません。
    #GoToやガード句の問題については過去にいろいろ論議されていますので、ご存じなければ一度調べた上でポリシーを決められると良いと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年10月26日 8:07
    モデレータ
  • 外池と申します。

         Try
    ReTry:

         Catch
              GoTo ReTry

         Finally

         End Try

    こういう制御がアリなのか? を、検証されてはいかがでしょう? ただ、私の第一印象としては「気持ち悪くてやりたくない」です。(GoTo文がコンパイルを通るのだから・・・、アリなのかなぁ。)

    再試行するループの入り口
         Try
              試行する動作
         Catch
              例外が発生した「状況」の整理
              (何か変数に「状況」を示すデータを入れておく)
         Finally

         End Try

         If (上述の変数を調べて)再試行すべき状況 Then ループ Else 試行打ち切り

    という構造にされては?


    (ホームページを再開しました)
    2011年10月26日 8:23
  •      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
    2011年10月26日 11:44
  • 外池です。

    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
    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年10月27日 2:36
  • VB での検証ありがとうございます。C# ではブランケットがブロックを決め、ラベルの有効範囲はブロックなので、try {} catch {} で別ブロックとなるためにラベルが参照できない、ということですね。VB では、ブロックがメソッド全域になるため、Try ~ Catch ~ で同じブロックにあり、ラベルが参照できるんですね。

    しかし、1回目の Finally で Dispose された frmMessageBox を使っているのかな~と思ったのですが、2回目以降は MessageBox を使っていますね。Dispose を複数回実行することは、それがあることを前提に実装することが勧められていますから、そのように実装されていることでしょう。なので、このあたりで例外が発生する要因は、無い様に思う。ますます「システムエラー」がなんなのか、気になる。。。


    Jitta@わんくま同盟
    2011年10月27日 15:48
  • なのでロジックがおかしかろうと「「システムエラー」とは、どのようなエラーが発生しているのでしょうか? もっと具体的に記述してください。」というコメントになるんですが…。

    「try~catchでリトライさせた中でのcatch」というタイトルなのでロジックに関する質問のように捉えがちですが、原因はそこではないと考えています。ロジックが何であれ、Webサービスでエラーが発生した後、リトライの仕方次第では再エラーになるのは当然でしょう。
    # Webサービスへ再接続が必要とかそういう問題じゃないかな、というのが最初からの予想。

    2011年10月28日 1:02
  • 佐祐理様、trapemiya様、外池様、Jitta様

    たくさんの回答ありがとうございました。

    つまるところ、リトライの為のGotoの飛び先をTryの外にしたら、2度目・3度目のCatchもうまく行きFinallyも動きました。

    いろいろと意識しなければいけない箇所等々大変参考になりました。

    ありがとうございました。

    2011年11月7日 0:55