DOBON.NET DOBON.NETプログラミング掲示板過去ログ

PictureBoxとListView再描画でフリーズ

環境/言語:[VB2005]
分類:[.NET]

10秒周期でアイコンを表示したPictureBox(frmMain)とアイコンとチェックボックス付ListView(frmList)を更新(再描画)する
アプリを作成しています。
起動直後は所望の動きなのですが、徐々に重たくなり数時間でフリーズします。
タスクマネージャーで監視すると
・メモリ使用量+仮想メモリサイズが徐々に増えている
 (1時間で25,25MB→50,50MB前後)
・CPU使用率も徐々に大きくなる

あと
・更新周期を30秒にすると重くなるのも遅くなる
・重くなると以下のエラーを吐く、場合によっては落ちる
'System.ArgumentException' の初回例外が System.Drawing.dll で発生しました。
'System.InvalidOperationException' の初回例外が System.Windows.Forms.dll で発生しました。
'System.Runtime.InteropServices.ExternalException' の初回例外が System.Drawing.dll で発生しました。
・アイコンファイル読込で落ちる場合あるが理由が不明
・1更新につき関数が呼ばれるのも1回であることは確認済

更新前にDisposeして必要分をNEWしているつもりなのですが
できていないのでしょうか?
それとも他に原因があるのでしょうか?
VB6の名残で標準モジュールで処理しているのがNG?

以下、該当部関数ソースを添付します。
(投稿用に一部名前変更/省略しています)
■No23220に返信(Strさんの記事)

試しに更新毎に下記3行を追加してみたらいがかでしょうか?
System.GC.Collect()
System.GC.WaitForPendingFinalizers()
System.GC.Collect()
■No23220に返信(Strさんの記事)
> 更新前にDisposeして必要分をNEWしているつもりなのですが
> できていないのでしょうか?

ぱっと見なので詳しくは分かりませんが、ImageListはDisposeしていないようですが...。

全てを作り直すことで再描画するのではなく、出来るだけ再利用することを考えた方が良いのではないでしょうか?
やじゅ様:
GCしてみましたが、状況変わらずです。
メモリ使用量も同じように増え続けています。

管理人様:
> ぱっと見なので詳しくは分かりませんが、ImageListはDisposeしていないようですが...。
> 全てを作り直すことで再描画するのではなく、出来るだけ再利用することを考えた方が良いのではないでしょうか?

毎回NEWせずに「Isnothingで調べて無ければNEWで確保」と変えてみましたが
変化無しです。

範囲が広すぎるので少しずつ切り分けて場所を特定中です。。。
> ぱっと見なので詳しくは分かりませんが、ImageListはDisposeしていないようですが...。

どぼんさんのご指摘以外で気になる点を指摘します。

●.Image = New Icon(KokiData(lngIndex - 1).strIconPath).ToBitmap
のIconがDisposeされていません。

●g2.DrawString("KOKI", New Font("MSゴシック", 6, FontStyle.Bold), Brushes.Black, 5, 10)
のFontがDisposeされていません。


.Parent = frmMain.PicMap

frmMain.PicMap.Controls.Add(frmMain.picIcon(lngIndex - 1))

この2行が意味的に重複していますが前半の.Parentの設定は不要では?

●以下の部分がForループで何個も作成されていますが、Form1つに対して1個で十分では?
Dim ttpMsg As ToolTip
ttpMsg = New ToolTip()

●上記のToolTipがDisposeされていません。


Catch ex As Exception

End Try
この箇所で例外を握りつぶしてしまっていますが、
例外が発生したりしていませんか?
何が起きるかわからないからとりあえずExceptionをCatchするというのは
不具合を隠すことになりますので、やめた方がよいです。

> 全てを作り直すことで再描画するのではなく、出来るだけ再利用することを考えた方が良いのではないでしょうか?

この点はどぼんさんと同意見です。
提示のソースコード内だけで大量のオブジェクトが作成(=大量のメモリを消費)されています。
10秒周期で更新とのことなので短時間におびただしい数のオブジェクトが不要になりますが、
GCが動くのは任意のタイミングなので、パフォーマンスの観点でもお勧めできない作りだと思います。

後、動的配列とRedimを使っているところがありますが、
List(Of T)を使うようにするとよいでしょう。
Redim Preserveの動作は新しいサイズの配列を新規確保し、
そこに元のデータをコピーし、元の配列は破棄するという処理が行われるので、
メモリを非常に無駄遣いします。極力使わない方がよいです。
返信ありがとうございます。

結論?から言いますと
> ●上記のToolTipがDisposeされていません。
このToolTipが直接の原因だったようで
コメントアウトすれば徐々に重くなる現象はなくなりました。
(一応直接の原因はわかったので「解決済み」とします)

そもそも参照元をDisposeすれば良い、と勘違いしていたようです。
で、これらをDisposeしようと思ったのですが

> ●.Image = New Icon(KokiData(lngIndex - 1).strIconPath).ToBitmap
> のIconがDisposeされていません。

これは
frmMain.picIcon(lngDelIndex).Image.Dispose()
ではダメでしょうか?
Imageだけで参照先のIconは残る?

> ●g2.DrawString("KOKI", New Font("MSゴシック", 6, FontStyle.Bold), Brushes.Black, 5, 10)
> のFontがDisposeされていません。
> ●上記のToolTipがDisposeされていません。

これらのDisposeも記述方法が?です。
関数の最後でDisposeすれば反映されませんし
10秒後にDisposeしようにもDim変数なので指定できません。


> ●
> .Parent = frmMain.PicMap
> と
> frmMain.PicMap.Controls.Add(frmMain.picIcon(lngIndex - 1))
> この2行が意味的に重複していますが前半の.Parentの設定は不要では?

おっしゃるとおりです。webから切り貼りしたので重複しておりました。

> ●以下の部分がForループで何個も作成されていますが、Form1つに対して1個で十分では?
> Dim ttpMsg As ToolTip
> ttpMsg = New ToolTip()

すいません、投稿用に変更した際のミスです。
意図は「1アイコンそれぞれに表示(1回設定)」で
本番(変更前)ソースでは意図通りに動いています。
(Disposeできていませんが・・・)


> ●
> Catch ex As Exception
> End Try
> この箇所で例外を握りつぶしてしまっていますが、
> 例外が発生したりしていませんか?

定常的に例外は出ていませんが
全くよろしくはありませんね。
(リリース時に入れといて例外で落ちにくくする、というのはあり?)


>>全てを作り直すことで再描画するのではなく、出来るだけ再利用することを考えた方が良いのではないでしょうか?
> この点はどぼんさんと同意見です。

10秒ごとに表示するアイコンが100個だったり1個だったり
可変するので安直に全部消去→必要分描画、としておりました・・・


> 後、動的配列とRedimを使っているところがありますが、
> List(Of T)を使うようにするとよいでしょう。

これも知りませんでした。
アドバイスありがとうございます。

VB6感覚でやるとNew〜Disposeが鬼門ですね・・・
解決済み!
2008/10/27(Mon) 15:11:47 編集(投稿者)

>>●.Image = New Icon(KokiData(lngIndex - 1).strIconPath).ToBitmap
>>のIconがDisposeされていません。

例えば、
Dim icon As New Icon(KokiData(lngIndex - 1).strIconPath)
.Image = icon.ToBitmap
icon.Dispose()
のようにすればよいかと。

> これは
> frmMain.picIcon(lngDelIndex).Image.Dispose()
> ではダメでしょうか?

はい、ダメです。

> Imageだけで参照先のIconは残る?

ToBitmapメソッドの呼び出しは、そのIconと同じ内容のBitmapを作成するメソッドで、
元のIconと新しく作成されたBitmapには何ら関係はありません。

>>●g2.DrawString("KOKI", New Font("MSゴシック", 6, FontStyle.Bold), Brushes.Black, 5, 10)
>>のFontがDisposeされていません。

こちらも先ほどの例のように一時変数でFontオブジェクトを一旦受けておけば、
後で問題なくDisposeできますね。

>>●上記のToolTipがDisposeされていません。
>
> これらのDisposeも記述方法が?です。
> 関数の最後でDisposeすれば反映されませんし
> 10秒後にDisposeしようにもDim変数なので指定できません。

変数ttpMsgをローカル変数で宣言して使うのではなく、
Formに配置しておいて、例のコードのようにToolTipオブジェクトを
何度もNewするようなことさえしなければOKでしょう。
(Formに配置しておけば、FormのDisposeタイミングでToolTipもDisposeされます)
解決済み!
いろいろご教示ありがとうございます。
本当に助かります。

で、プログラムも改修しているのですが
(関数も標準モジュールから該当フォームに移動しました)


> 変数ttpMsgをローカル変数で宣言して使うのではなく、
> Formに配置しておいて、例のコードのようにToolTipオブジェクトを
> 何度もNewするようなことさえしなければOKでしょう。
> (Formに配置しておけば、FormのDisposeタイミングでToolTipもDisposeされます)

ここがやはりダメ?みたいです。
フォームデザイナでフォームにToolTip(ttpIcon)を貼り付け

'ToolTip設定
'Dim ttpMsg As ToolTip 'コメントアウト
'ttpMsg = New ToolTip() 'コメントアウト

'ToolTipが表示されるまでの時間
Me.ttpIcon.InitialDelay = 200
Me.ttpIcon.SetToolTip(frmMain.picZrAll(lngIndex), "接続中")

としたのですが、実行すると徐々に重くなります。
(1時間稼動で画面更新時CPU使用率が50%超える)
下2行もコメントアウトすると数時間稼動しても特に重くなりません。

やり方が間違ってるのでしょうか?
それとも他に原因があって、ここで顕著に出てるだけ?
長々とスイマセンがよろしくお願いします。

DOBON.NET | プログラミング道 | プログラミング掲示板