トップ回答者
PictureBox.ImageのDisposeタイミングについて

質問
-
FormにpictureBox1とbutton1があり、button1のクリックイベントに以下の記述があるとします。
(無意味かつ無駄な処理かもしれませんが、質問のためのサンプルということでご了承下さい)
Code Snippetprivate void button1_Click(object sender, System.EventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Blue);
}
pictureBox1.Image = bmp;
}
GCが1回で済むように明示的にDisposeを呼ぶ場合、pictureBox1.Image = bmp; の部分を
Code Snippetif (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose(); // (1)
}
pictureBox1.Image = bmp; // (2)
それとも、(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて
Code SnippetImage img = pictureBox1.Image;
pictureBox1.Image = bmp;
if (img != null)
{
img.Dispose();
}
ご回答よろしくお願いいたします。
回答
-
マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。
pictureBox1.Image にnull は入れとかなきゃいけないですね。
>(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて
マルチスレッドで「pictureBox1.Image」に操作するんであればこれじゃだめです。
Code SnippetImage img = pictureBox1.Image; //(1)
pictureBox1.Image = bmp;
if (img != null) //(2)
{
img.Dispose();
}1と2の間で、pictureBox1.Image を Dispose() されると困るし pictureBox1.Image にnullも入れてないし。
マルチスレッドについてはこの辺を読んでください。
http://msdn2.microsoft.com/ja-jp/library/3e8s7xdd(VS.80).aspx
-
akari さんからの引用
Dispose()は複数回呼んでも大丈夫なはずなので、その点は気にしなくてもいいのではないでしょうか?
ただ、(1)と(3)の間でpictureBox1.Imageに別なImageオブジェクトが代入されると、そのオブジェクトはGCが2回必要になるという問題はありそうです。
マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。WinForms もそうですし,多くの COM ベースの UI コンポーネントもそうなのですが,基本的に Windows の UI に関するコンポーネントは STA を前提としています.つまり,すべての操作は単一の UI スレッドから行われる (直列化される) ことが期待されています.そのため,全く別の物理スレッドから UI の状態が変更されるという可能性はありません (し,そのような操作を行ってはいけません).UI コンポーネントに対してダブルチェックロッキングパターンが必要になるような事態は,たいてい何か間違っています.
問題になるのは,ある UI 処理が終わる前にメッセージディスパッチャが実行され,別の UI 処理に割り込まれることです.ドライバの IRP と割り込みレベルのように,メッセージ処理状態に優先度が存在すれば,もう少し対処のしようもあったのですが……
あるメッセージ応答の進行中に.そのスタックの上にのっかる形でべつのハンドラが実行されると,かなりややこしいことになります.安易な DoEvents が嫌われるのはこういった事情ですかね.
すべての返信
-
マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。
pictureBox1.Image にnull は入れとかなきゃいけないですね。
>(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて
マルチスレッドで「pictureBox1.Image」に操作するんであればこれじゃだめです。
Code SnippetImage img = pictureBox1.Image; //(1)
pictureBox1.Image = bmp;
if (img != null) //(2)
{
img.Dispose();
}1と2の間で、pictureBox1.Image を Dispose() されると困るし pictureBox1.Image にnullも入れてないし。
マルチスレッドについてはこの辺を読んでください。
http://msdn2.microsoft.com/ja-jp/library/3e8s7xdd(VS.80).aspx
-
ご回答ありがとうございます。
えムナウ さんからの引用 マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。
マルチスレッドを意識したプログラミングをしていない限り、メソッド(button1_Click)の実行中にFormの再描画が行われる可能性もないということですね。最初の質問に関してはこれで解決とします。
えムナウ さんからの引用 pictureBox1.Image にnull は入れとかなきゃいけないですね。
最初のサンプルの挙動(bmpを代入)を変えないとすると、nullの代入は必要ない気がするのですが違うのでしょうか?
えムナウ さんからの引用 Code SnippetImage img = pictureBox1.Image; //(1)
pictureBox1.Image = bmp; //(3)
if (img != null) //(2)
{
img.Dispose();
}1と2の間で、pictureBox1.Image を Dispose() されると困るし pictureBox1.Image にnullも入れてないし。
Dispose()は複数回呼んでも大丈夫なはずなので、その点は気にしなくてもいいのではないでしょうか?
ただ、(1)と(3)の間でpictureBox1.Imageに別なImageオブジェクトが代入されると、そのオブジェクトはGCが2回必要になるという問題はありそうです。
マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。 -
akari さんからの引用
Dispose()は複数回呼んでも大丈夫なはずなので、その点は気にしなくてもいいのではないでしょうか?
ただ、(1)と(3)の間でpictureBox1.Imageに別なImageオブジェクトが代入されると、そのオブジェクトはGCが2回必要になるという問題はありそうです。
マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。WinForms もそうですし,多くの COM ベースの UI コンポーネントもそうなのですが,基本的に Windows の UI に関するコンポーネントは STA を前提としています.つまり,すべての操作は単一の UI スレッドから行われる (直列化される) ことが期待されています.そのため,全く別の物理スレッドから UI の状態が変更されるという可能性はありません (し,そのような操作を行ってはいけません).UI コンポーネントに対してダブルチェックロッキングパターンが必要になるような事態は,たいてい何か間違っています.
問題になるのは,ある UI 処理が終わる前にメッセージディスパッチャが実行され,別の UI 処理に割り込まれることです.ドライバの IRP と割り込みレベルのように,メッセージ処理状態に優先度が存在すれば,もう少し対処のしようもあったのですが……
あるメッセージ応答の進行中に.そのスタックの上にのっかる形でべつのハンドラが実行されると,かなりややこしいことになります.安易な DoEvents が嫌われるのはこういった事情ですかね.
-
>最初のサンプルの挙動(bmpを代入)を変えないとすると、nullの代入は必要ない気がするのですが違うのでしょうか?
if (img != null) で何のためにnullチェックしているのでしょうか?
このページのグラフィックを消去するにはを見てください。
http://msdn2.microsoft.com/ja-jp/library/t94wdca5(VS.80).aspx
>マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。
素直にInvokeで一つのスレッドに渡して処理してください。
-
NyaRuRu さんからの引用 WinForms もそうですし,多くの COM ベースの UI コンポーネントもそうなのですが,基本的に Windows の UI に関するコンポーネントは STA を前提としています.つまり,すべての操作は単一の UI スレッドから行われる (直列化される) ことが期待されています.そのため,全く別の物理スレッドから UI の状態が変更されるという可能性はありません (し,そのような操作を行ってはいけません).
なるほど。最初の例自体がナンセンスだったわけですね。
NyaRuRu さんからの引用 問題になるのは,ある UI 処理が終わる前にメッセージディスパッチャが実行され,別の UI 処理に割り込まれることです.
これは具体的にはどういったケースで起こるのでしょうか?
えムナウ さんからの引用 if (img != null) で何のためにnullチェックしているのでしょうか?
最初の例では、「初めてbutton1が押された場合」および「外部からpictureBox1.Imageがnullに書き換えられた場合」を想定していましたが、後者は考慮する必要がないと分かったので、現在は前者の目的のみです。
グラフィックを消去するのならnullの代入が必要なのは分かりますが、新しいBitmapインスタンスで置き換える場合はやはりnull代入は必要ないと思います。
(この話題は本筋から逸脱しているので、これ以上触れなくても構いません)
えムナウ さんからの引用 素直にInvokeで一つのスレッドに渡して処理してください。
どうしても明示的な排他制御が必要な場合は、ということですよね。
今までの話から、変な作りにしなければ通常はUIコンポーネントの排他制御自体気にしなくていいと理解しています。