none
PictureBox.ImageのDisposeタイミングについて RRS feed

  • 質問

  • FormにpictureBox1とbutton1があり、button1のクリックイベントに以下の記述があるとします。
    (無意味かつ無駄な処理かもしれませんが、質問のためのサンプルということでご了承下さい)

    Code Snippet
    private 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;
    }

     

    上記のコードだとボタンを2回以上押したときにpictureBox1.Imageが指すBitmapオブジェクトがGCの対象になりますが、Disposeしていないので完全に回収されるまでGCが2回必要になるはずです。
    GCが1回で済むように明示的にDisposeを呼ぶ場合、pictureBox1.Image = bmp; の部分を

    Code Snippet
        if (pictureBox1.Image != null)
        {
            pictureBox1.Image.Dispose(); // (1)
        }
        pictureBox1.Image = bmp; // (2)

     

    と書き換えれば十分でしょうか?
    それとも、(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて

    Code Snippet
        Image img = pictureBox1.Image;
        pictureBox1.Image = bmp;
        if (img != null)
        {
            img.Dispose();
        }

     

    としなければならないのでしょうか?
    ご回答よろしくお願いいたします。
    2007年7月18日 1:25

回答

  • マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。

    pictureBox1.Image にnull は入れとかなきゃいけないですね。

     

    >(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて

    マルチスレッドで「pictureBox1.Image」に操作するんであればこれじゃだめです。

     

     

    Code Snippet
        Image 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

     

     

    2007年7月18日 3:48
  •  akari さんからの引用


    Dispose()は複数回呼んでも大丈夫なはずなので、その点は気にしなくてもいいのではないでしょうか?
    ただ、(1)と(3)の間でpictureBox1.Imageに別なImageオブジェクトが代入されると、そのオブジェクトはGCが2回必要になるという問題はありそうです。
    マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。

     

    WinForms もそうですし,多くの COM ベースの UI コンポーネントもそうなのですが,基本的に Windows の UI に関するコンポーネントは STA を前提としています.つまり,すべての操作は単一の UI スレッドから行われる (直列化される) ことが期待されています.そのため,全く別の物理スレッドから UI の状態が変更されるという可能性はありません (し,そのような操作を行ってはいけません).UI コンポーネントに対してダブルチェックロッキングパターンが必要になるような事態は,たいてい何か間違っています.

     

    問題になるのは,ある UI 処理が終わる前にメッセージディスパッチャが実行され,別の UI 処理に割り込まれることです.ドライバの IRP と割り込みレベルのように,メッセージ処理状態に優先度が存在すれば,もう少し対処のしようもあったのですが……

    あるメッセージ応答の進行中に.そのスタックの上にのっかる形でべつのハンドラが実行されると,かなりややこしいことになります.安易な DoEvents が嫌われるのはこういった事情ですかね.

    2007年7月19日 7:09

すべての返信

  • マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。

    pictureBox1.Image にnull は入れとかなきゃいけないですね。

     

    >(1)と(2)の間でpictureBox1.Imageがアクセスされる可能性まで考えて

    マルチスレッドで「pictureBox1.Image」に操作するんであればこれじゃだめです。

     

     

    Code Snippet
        Image 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

     

     

    2007年7月18日 3:48
  • ご回答ありがとうございます。

     えムナウ さんからの引用
    マルチスレッドで「pictureBox1.Image」に操作していない限りどちらでもいいです。

    マルチスレッドを意識したプログラミングをしていない限り、メソッド(button1_Click)の実行中にFormの再描画が行われる可能性もないということですね。最初の質問に関してはこれで解決とします。

     えムナウ さんからの引用
    pictureBox1.Image にnull は入れとかなきゃいけないですね。

    最初のサンプルの挙動(bmpを代入)を変えないとすると、nullの代入は必要ない気がするのですが違うのでしょうか?

     えムナウ さんからの引用

    Code Snippet
        Image 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回必要になるという問題はありそうです。
    マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。

    2007年7月19日 6:45
  •  akari さんからの引用


    Dispose()は複数回呼んでも大丈夫なはずなので、その点は気にしなくてもいいのではないでしょうか?
    ただ、(1)と(3)の間でpictureBox1.Imageに別なImageオブジェクトが代入されると、そのオブジェクトはGCが2回必要になるという問題はありそうです。
    マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。

     

    WinForms もそうですし,多くの COM ベースの UI コンポーネントもそうなのですが,基本的に Windows の UI に関するコンポーネントは STA を前提としています.つまり,すべての操作は単一の UI スレッドから行われる (直列化される) ことが期待されています.そのため,全く別の物理スレッドから UI の状態が変更されるという可能性はありません (し,そのような操作を行ってはいけません).UI コンポーネントに対してダブルチェックロッキングパターンが必要になるような事態は,たいてい何か間違っています.

     

    問題になるのは,ある UI 処理が終わる前にメッセージディスパッチャが実行され,別の UI 処理に割り込まれることです.ドライバの IRP と割り込みレベルのように,メッセージ処理状態に優先度が存在すれば,もう少し対処のしようもあったのですが……

    あるメッセージ応答の進行中に.そのスタックの上にのっかる形でべつのハンドラが実行されると,かなりややこしいことになります.安易な DoEvents が嫌われるのはこういった事情ですかね.

    2007年7月19日 7:09
  • >最初のサンプルの挙動(bmpを代入)を変えないとすると、nullの代入は必要ない気がするのですが違うのでしょうか?

    if (img != null) で何のためにnullチェックしているのでしょうか?

     

    このページのグラフィックを消去するにはを見てください。

    http://msdn2.microsoft.com/ja-jp/library/t94wdca5(VS.80).aspx

     

    >マルチスレッドを考慮する場合はダブルチェックロッキングとか使ってうまくやらないといけないですね。

    素直にInvokeで一つのスレッドに渡して処理してください。

     

    2007年7月19日 13:50
  •  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コンポーネントの排他制御自体気にしなくていいと理解しています。

    2007年7月20日 2:56