@IT会議室は、ITエンジニアに特化した質問・回答コミュニティ「QA@IT」に生まれ変わりました。ぜひご利用ください。
- PR -

インデックスが配列の境界外です。

投稿者投稿内容
ホーガン
常連さん
会議室デビュー日: 2008/02/18
投稿数: 42
投稿日時: 2009-04-13 11:52
ASP.NETで開発しているのですが、以下のコードで「System.IndexOutOfRangeException: インデックスが配列の境界外です。」というエラーが発生します。今まで6か月間動作していますが、このエラーは初めてで再現性が
ありません。6か月間毎日動作していて、先日1回発生しただけです。
別に配列も参照していないと思うのですが、どなたかエラー原因の可能性を教えて
頂けませんでしょうか。
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Public Shared Sub sMsgRead()
Dim sb As StringBuilder
Dim wRet As UInt32
Dim wSec() As String = {"INFO", "WARNING", "ERROR"} ←ここでエラーが発生します。
Dim wKey() As String = {"INF", "WAR", "ERR"}
Dim wKeyStr As String

Dim wLoop As Integer
Dim wStr As String
Dim wCnt As Integer

ReDim mMsgI(0)
ReDim mMsgW(0)
ReDim mMsgE(0)

mMsgCntI = 0
mMsgCntW = 0
mMsgCntE = 0

If Not File.Exists(mIniFileName) Then Exit Sub

sb = New StringBuilder(BUFF_LEN)

For wLoop = 0 To 2
wCnt = 0

Do While True
wKeyStr = wKey(wLoop) + String.Format("{0,3:000}", wCnt + 1)

wRet = GetPrivateProfileString(wSec(wLoop), wKeyStr, "X", sb, Convert.ToUInt32(sb.Capacity), mIniFileName)
wStr = sb.ToString()

If wStr = "X" Then Exit Do
wCnt += 1

Select Case wLoop
Case 0
mMsgCntI = wCnt
ReDim Preserve mMsgI(wCnt)
With mMsgI(wCnt - 1)
.mMsgCd = wStr.Split(":")(0)
.mMsgLevel = "I"
.mMsgContent = wStr.Split(":")(1)
End With
Case 1
mMsgCntW = wCnt
ReDim Preserve mMsgW(wCnt)
With mMsgW(wCnt - 1)
.mMsgCd = wStr.Split(":")(0)
.mMsgLevel = "W"
.mMsgContent = wStr.Split(":")(1)
End With
Case 2
mMsgCntE = wCnt
ReDim Preserve mMsgE(wCnt)
With mMsgE(wCnt - 1)
.mMsgCd = wStr.Split(":")(0)
.mMsgLevel = "E"
.mMsgContent = wStr.Split(":")(1)
End With
End Select

Loop
Next
End Sub
King
ぬし
会議室デビュー日: 2008/06/20
投稿数: 284
投稿日時: 2009-04-13 12:52
指定の箇所の記述はコンパイルが通る事から間違っていないと思います。

気になるのは

1.関数が Shared であること。
2.Web なので複数のユーザが同時に実行する可能性があること。
3.再現性がないこと。

というところでしょうか。
はっきりとはわかりませんが、静的なフィールドに複数ユーザが同時アクセスした関係とか・・・。
例えばユーザAによって wSec の配列数が確定した後にユーザBによって配列の初期化が起こったためとか。
ホーガン
常連さん
会議室デビュー日: 2008/02/18
投稿数: 42
投稿日時: 2009-04-13 13:01
Kingさんのご指摘通り、Shared が怪しいと思うのですが
Sharedを使用しないで
単純にPublic Sub sMsgRead()すれば、ユーザーAを、ユーザーBも
各々の動作に影響されることなく使用できるという事になるのでしょうか。
初歩的な質問ですいません。
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2009-04-13 13:39
引用:

Kingさんの書き込み (2009-04-13 12:52) より:
はっきりとはわかりませんが、静的なフィールドに複数ユーザが同時アクセスした関係とか・・・。
例えばユーザAによって wSec の配列数が確定した後にユーザBによって配列の初期化が起こったためとか。



wSecはローカル変数なので関係ないと思いますが、
mMsgI、mMsgW、mMsgEの3つ配列はローカル変数ではないようなので、
各アクセスで同じ変数が設定されるとしたら、
IndexOutOfRangeExceptionが起きる可能性がありますね。

sMsgReadメソッドはどこのクラスに定義されているんでしょう?
mMsgIなどの変数(mの付いている変数全般)はどこでどのように定義しているのでしょう?

sMsgReadメソッドがSharedかどうかというより、
ローカル変数以外の変数へのアクセスで、複数ユーザによる同時アクセス時に
同じ変数を設定する箇所があれば、問題が発生する可能性があります。
今提示されているコードだけからではその辺どうなっているかわからないので、
なんとも言えません。
King
ぬし
会議室デビュー日: 2008/06/20
投稿数: 284
投稿日時: 2009-04-13 13:42
影響があるかないかで言えばあるでしょうね。
インスタンス生成なしで利用できていた関数が
インスタンスを生成し単純に静的な変数なら共有するけど、そうでなければ各々のインスタンスを持ちます。
ただ Shared を使用しなかったら他に影響が出るのではないでしょうか。
インスタンス生成無しで利用できていた関数が
インスタンスを生成しないと利用できなくなるわけですから。

それにこの事が原因だと決まったわけではないですし。

[ メッセージ編集済み 編集者: King 編集日時 2009-04-13 14:03 ]
ホーガン
常連さん
会議室デビュー日: 2008/02/18
投稿数: 42
投稿日時: 2009-04-13 14:54
よねKENさん、Kingさん回答ありがとうございます。

引用−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
sMsgReadメソッドはどこのクラスに定義されているんでしょう?
mMsgIなどの変数(mの付いている変数全般)はどこでどのように定義しているのでしょう?
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
sMsgReadメソッドは、WsysComMsgというクラスの中で定義しています。
setIniPathもWsysComMsgというクラスの中で定義しています
同じクラスでm付きの変数を以下の様に定義しています。
'-------------------------------------------------------------
' メッセージデータ
'-------------------------------------------------------------
Public Structure tMsg
Dim mMsgCd As String 'メッセージコード
Dim mMsgLevel As String 'レベル
Dim mMsgContent As String '内容
End Structure
Private Shared mMsgI() As tMsg 'Infoメッセージ
Private Shared mMsgW() As tMsg 'Warningメッセージ
Private Shared mMsgE() As tMsg 'Errorメッセージ

Private Shared mMsgCntI As Integer 'Infoメッセージ数
Private Shared mMsgCntW As Integer 'Warningメッセージ数
Private Shared mMsgCntE As Integer 'Errorメッセージ数

'-------------------------------------------------------------
' INIファイルPATH設定
'-------------------------------------------------------------
Public Shared Sub setIniPath(ByVal pPath As String)
mIniFileName = WSysCom.getPathAddBackslash(pPath) & INIFILE_NAME
sMsgRead()
End Sub
'-------------------------------------------------------------
' メッセージ内容の取得
'-------------------------------------------------------------
Public Shared ReadOnly Property getMsgContent(ByVal pMsgCd As String) As String
Get
Dim wMsgStr As String = ""

wMsgStr = getMsgContent("I", pMsgCd)
If wMsgStr = "" Or wMsgStr = pMsgCd Then
wMsgStr = getMsgContent("W", pMsgCd)

If wMsgStr = "" Or wMsgStr = pMsgCd Then
wMsgStr = getMsgContent("E", pMsgCd)
End If
End If
Return wMsgStr
End Get
End Property

Public Shared ReadOnly Property getMsgContent(ByVal pMsgLevel As String, ByVal pMsgCd As String) As String
Get
Dim wLoop As Integer
Dim wMsg() As tMsg
Dim wCnt As Integer
Dim wMsgStr As String = ""

Select Case pMsgLevel
Case "I"
wMsg = mMsgI
wCnt = mMsgCntI
Case "W"
wMsg = mMsgW
wCnt = mMsgCntW
Case "E"
wMsg = mMsgE
wCnt = mMsgCntE
End Select

For wLoop = 0 To wCnt - 1
If wMsg(wLoop).mMsgCd = pMsgCd Then
wMsgStr = wMsg(wLoop).mMsgContent
Exit For
End If
Next

If wMsgStr = "" Then
wMsgStr = pMsgCd
End If
Return wMsgStr
End Get
End Property


各画面からaspx.vbのPage_Load時に

WSysComMsg.setIniPath(Request.PhysicalApplicationPath)

と記述してm付きの変数に値を設定し、エラーが発生した
場合、aspxで以下の記述でメッセージを取得しています。

response.write("flg = vbMsgbox(""" & Syscom.WSyscomMsg.getMsgContent("E","RKTM003E") & """,msgOKOnly + msgInformation ,""エラーダイアログ"");" & vbCrLf)


全体的なソースの流れは、上記の様な感じです。
インスタンスを生成しないといけないのでしょうか。(原因は、Sharedとした場合)

King
ぬし
会議室デビュー日: 2008/06/20
投稿数: 284
投稿日時: 2009-04-13 21:24
よねKENさんのおっしゃるように Shared は関係ないと思います。
適当な事言って申し訳ありませんでした。

ただ Shared じゃ無くなったら解決するとしても
なぜ現時点で Shared で実装しているのか、と言う事をはっきりさせておかないと
影響範囲等わかりませんし危ないと思います。
もし把握されてるならいいのですが。
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2009-04-14 10:08
WsysComMsgというクラスはおそらくSharedな変数やメソッドしかなく、
このWebアプリ全体で共通の処理がまとめてあるのではありませんか?
(ComはCommonの意味なのでしょうね・・・)

ユーザAのアクセスにより、例えば、sMsgReadメソッドの以下の箇所を実行中だとしましょう。
引用:

ホーガンさんの書き込み (2009-04-13 11:52) より:

ReDim mMsgI(0)
ReDim mMsgW(0)
ReDim mMsgE(0)
実行箇所(A)→
mMsgCntI = 0
mMsgCntW = 0
mMsgCntE = 0




同じタイミングでユーザBのアクセスがあり、getMsgContent(ByVal pMsgLevel As String, ByVal pMsgCd As String)プロパティの以下の箇所が実行中だとしましょう。

引用:

ホーガンさんの書き込み (2009-04-13 14:54) より:

Select Case pMsgLevel
Case "I"
wMsg = mMsgI
実行箇所(B)→
wCnt = mMsgCntI
Case "W"
wMsg = mMsgW
wCnt = mMsgCntW
Case "E"
wMsg = mMsgE
wCnt = mMsgCntE
End Select

For wLoop = 0 To wCnt - 1
If wMsg(wLoop).mMsgCd = pMsgCd Then
wMsgStr = wMsg(wLoop).mMsgContent
Exit For
End If
Next



ユーザAのための処理で(A)に来た時点で、mMsgIは要素数1の配列になります。
しかし、mMsgCntIは前回の実行した際の値ですので、0とは限りません。仮にNとします。
この状態で、ユーザBのための処理は(B)の場所を実行しようとしています。
wMsg変数はmMsgI変数の指すオブジェクトを参照しているので、要素数1の配列です。
wCnt = mMsgCntIが実行されるとwCntはNとなります。
このように配列の要素数の実際の値と配列の要素数のカウントを保持している変数の間で不整合が起きます。
この例では、後のFor文の実行でN回ループするので、要素数1の配列の境界外をアクセスすることになり、IndexOutOfBoundExceptionが起きることになります。

#ASP.NETってマルチスレッドで動いているんですよね???>ASP.NETの偉い人
スレッドセーフな作りではないからだと思います。

修正の前に、まずは上記の説明を踏まえて、再現方法を確保するのが先決だと思います。
再現できなければ、修正してもその修正が正しいかどうかの担保が取れませんから。

その上で、スレッドセーフについて調べてみてください。

WSysComMsgクラスのようなSharedな変数、Sharedなメンバ(プロパティ、メソッド)を使っている箇所は全部インスタンス変数、メンバだけを使うように改造し、このクラスを使う各画面では、常にWSysComMsgのインスタンスを作って処理し、このインスタンスを一切使い回しをしないようにすれば、とりあえず今回のような問題は起きなくなると思います。
#他にも対処方法はいろいろあるでしょうけれど、ご提示のソースコードを見る限りでは、
#Sharedを一切使うな、という形にするのが安全そうな気がします。

WSysComMsgだけでなく、同様の作りになっている箇所があれば、それらも修正が必要でしょう。

「ASP.NET Shared変数」「ASP.NET static変数」で検索するといろいろ
共有変数を使った場合の問題点や対策案がわかると思います。


[ メッセージ編集済み 編集者: よねKEN 編集日時 2009-04-14 10:10 ]

スキルアップ/キャリアアップ(JOB@IT)