none
bckgrondWorkerで取得したDBのデータをdataGridViewに表示するとメモリを大量に消費するのですが… RRS feed

  • 質問

  • 初めて投稿させていただきます。
    C#の勉強を始めて、入門書を読みながら、ようやくデータベース(Access 2007)への接続~dataGridViewで表示ができるようになりました。

    データベースのレコード数は5,000件ほどあるので、フォームの表示が遅くなってしまうため、データの取得はbackgroundWorkerで行っています。
    なんとかデータの取得と表示はできるのですが、フォームが表示された後も、延々とメモリを消費し続けてしまいます。
    フォームに配置しているボタンやコンボボックスなどの操作はできます。

    フォーラムに書かれている内容などを色々試してみたのですが解決できず、どこに問題があるのか自分では見つけることができませんでした。
    どなたかお分かりになる方、ご指摘 ・ ご指導をお願いします。

    //=========   ソース ここから(一部略しています)    ================
    using System;
    using System.Data;
    using System.Data.OleDb;
    using System.Drawing;
    using System.Windows.Forms;
    using System.ComponentModel;

    public partial class Sample : Form {

    OleDbConnection cn = new OleDbConnection([接続文字列]);
    DataSet dSet;
    BindingSource binding;

    // コンストラクタ
    public Sample() {

        InitializeComponent();

        // フォーム上のコンボボックスに使用するための項目をDBから取得する処理など

        // dataGridView1の設定
        this.dataGridView1.AllowUserToAddRows = false;
        this.dataGridView1.AllowUserToDeleteRows = false;
        this.dataGridView1.AllowUserToOrderColumns = true;
        this.dataGridView1.ReadOnly = true;
        this.dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
        this.dataGridView1.MultiSelect = false;
        this.dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
        this.dataGridView1.AllowUserToResizeColumns = true;
        this.dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
        this.dataGridView1.AllowUserToResizeRows = false;
        this.dataGridView1.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing;
        this.dataGridView1.RowTemplate.Height = 30;
        this.dataGridView1.ColumnHeadersHeight = 30;
    }

    // フォームのロードイベント
    private void Sample_Load(object sender, EventArgs e) {
        if (!backgroundWorker1.IsBusy) {
            backgroundWorker1.RunWorkerAsync();
        }
    }



    private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
        BackgroundWorker worker = sender as BackgroundWorker;
        if ((worker.CancellationPending == true)) {
            return;
        } else {
             try {
                 string sqlCommand = "[SQLのSELECTコマンド]";

                 using (OleDbDataAdapter dAdp = new OleDbDataAdapter(sqlCommand, cn)) {
                      dSet = new DataSet("顧客リスト");
                      dAdp.Fill(dSet, "顧客リスト");
                  }
                  binding = new BindingSource(dSet, "顧客リスト");
             } catch (Exception) {
                   MessageBox.Show(ex.Message);
             }
        }
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e){
        if(e.Error != null){
            MessageBox.Show(e.Error.Message);  
        } else {
            // バインディングナビゲーター
            // 件数をみると、正常にデータは取得できています。
            this.bindingNavigator1.BindingSource = binding;

            // これ以降の行をコメント化せずに実行すると、延々とメモリを表示してしまいます。
            this.dataGridView1.DataSource = binding;
            this.dataGridView1.Columns[0].Width = 100;
            this.dataGridView1.Columns[0].Name = "顧客ID";
            this.dataGridView1.Columns[1].Width = 100;
            this.dataGridView1.Columns[1].Name = "顧客名";
            this.dataGridView1.Columns[2].Width = 200;
            this.dataGridView1.Columns[2].Name = "住所";
            this.dataGridView1.Columns[3].Width = 200;
            this.dataGridView1.Columns[3].Name = "電話番号";
        // 以下略 20カラムほどあります。
        }
    }
    }

    //------------------------------------------------------------------------------------------------------
    追記します。
    投稿前に検証が足りなくて申し訳ありません。

    メモリを徐々に消費していくのは、backgroundWorkerを使ってデータを取得した場合とは限らないようです。
    dataGridViewにスクロールバーが表示された場合は、20件程度でもメモリを消費し続けるようです。
    反対に、スクロールバーが表示されない程度のデータ量であれば、メモリの消費量は増えませんでした。
    スクロールバーに、メモリ消費する原因があるのでしょうか?

    2012年5月19日 12:45

回答

  • > BackgroundWorkerを使わずに実行した場合も、同じ症状でした。

    ということは以下のようなおもいっきり最小限のものでも現象が起きているということでしょうか。

    private void Form1_Load(object sender, EventArgs e)
    {
        string conn_str = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db1.mdb";
    
        string sqlCommand = "SELECT * FROM test_table";
    
        using (OleDbDataAdapter dAdp = new OleDbDataAdapter(sqlCommand, conn_str))
        {
            DataTable dt = new DataTable();
            dAdp.Fill(dt);
            dataGridView1.DataSource = dt;
        }
    }

    もしそうであれば、プログラムというより環境の問題になってしまうと思います。
    環境 Intel i5(4コア)、 Win7 32bit、 4GBメモリ、 VisualStudio2008(.NET 3.5)、Access2002 で上記のコードでやってみましたが
    20カラム5000件のデータで実行時20MB強ぐらいのメモリ使用量でした。

    この最小限のコードなら問題ない、という場合は、ひとつずつ機能を足していって確認することで
    どこが問題になっていそうかわかると思います。

    > 実害といえば、動作が非常に緩慢になります。
    > あと、実害というほどではないのですが、CPUのファン(?)の音が、思い切り回り続けます。
    > きっと何か無駄な処理をしてしまっているのだと思っています。

    CPU100%で無限ループしている時とか、そんなふうになりますね。

    2012年5月22日 2:32

すべての返信

  • 1. どこかのイベントが何回も必要ないのに呼び出されているとか無いですかね?
    2. BindingSourceを使わないで、直接DataTableをDataGridView.DataSourceに指定したらどうなりますか。
    3. (関係ないと思いますが)this.dataGridView1.Columns[0].Width = 100; みたいなDataGridViewの設定はここじゃなくForm_Loadとかのタイミングで1回やればよいですよね?

    追記:
    > 延々とメモリを消費し続けてしまいます。

    メモリ消費が増えているのをどうやって確認しましたか?
    それによる実害は何か起きていますか?
    また、BackGroundWorker使わないときはうまくいってるのですか?
    なども情報としては必要かもしれません。
    というか、そっちのほうが取っ掛かりとしては必要な情報と思います。

    • 編集済み mars12 2012年5月21日 8:06 追記
    2012年5月21日 0:55
  • mars12様、ご回答ありがとうございます。

    > 1. どこかのイベントが何回も必要ないのに呼び出されているとか無いですかね?
    おっしゃる通り、FormのActivateイベントで、あるメソッドが何度も呼び出されていました。
    このメソッドは、質問には書いていないものです。
    これが原因だと思い、Activateイベントを一時的に削除してみたのですが、現象は変わりませんでした。

    >2. BindingSourceを使わないで、直接DataTableをDataGridView.DataSourceに指定したらどうなりますか。
    まだ試していませんが、後程、こちらの結果をご報告いたします。
    ですが、DataGridView.DataSourceにDataTableを指定した場合、BindingNavigatorのBindingSourceはどのように設定すればよいのでしょうか?
    以下のようにすると、DataGridViewとBindingNavigatorが連動(?)しなかったような気がします。
    DataGridView1.DataSource = dTbl;
    BindingNavigator1.BindingSource = bindingSrc;

    >3. this.dataGridView1.Columns[0].Width = 100; みたいなDataGridViewの設定はここじゃなくForm_Loadとかのタイミングで1回やればよいですよね?
    Form_Loadの時点では、まだデータベースからDataTableが取得できていない可能性があるので、列の数が一致しないなどのエラーになるように思い、backgroundWorker1_RunWorkerCompleted内で行っていました。
    確かに、データベースに接続するたびに表示の設定を行うのは、無駄だとは思うのですが…。

    メモリの消費が増えていくのは、[タスクマネージャー] ⇒ [プロセス] ⇒ [メモリ(プライベートワーキング)]の数値が際限なく増えていくことで確認しました。
    また、backgroundWorker1_RunWorkerCompletedの処理が終わっても、いつまでもCPUが00にならず、何らかの処理をしているようなのです。
    フォームにある別のボタンなどを押すと、そのボタンに関連付けたイベントの終了後に、メモリの増加が止まり、CPUも00になります。

    BackgroundWorkerを使わずに実行した場合も、同じ症状でした。
    DataGridViewに表示する内容が5,000件ありますので、これの表示などがメモリを消費するのかと思っていましたが、ほとんどが数値(顧客IDや電話番号など)で、それほど多いデータ量ではないにもかかわらず、数GBもメモリを消費するのはおかしいと思っているのです。
    あるいは、そういうものなのでしょうか?

    実害といえば、動作が非常に緩慢になります。
    あと、実害というほどではないのですが、CPUのファン(?)の音が、思い切り回り続けます。
    きっと何か無駄な処理をしてしまっているのだと思っています。

    勉強し始めたばかりなので、どんなことでも参考になります。
    「これを試すべき」などございましたら、ぜひご指導ください。
    よろしくお願いいたします。

    2012年5月21日 13:52
  • > BackgroundWorkerを使わずに実行した場合も、同じ症状でした。

    ということは以下のようなおもいっきり最小限のものでも現象が起きているということでしょうか。

    private void Form1_Load(object sender, EventArgs e)
    {
        string conn_str = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db1.mdb";
    
        string sqlCommand = "SELECT * FROM test_table";
    
        using (OleDbDataAdapter dAdp = new OleDbDataAdapter(sqlCommand, conn_str))
        {
            DataTable dt = new DataTable();
            dAdp.Fill(dt);
            dataGridView1.DataSource = dt;
        }
    }

    もしそうであれば、プログラムというより環境の問題になってしまうと思います。
    環境 Intel i5(4コア)、 Win7 32bit、 4GBメモリ、 VisualStudio2008(.NET 3.5)、Access2002 で上記のコードでやってみましたが
    20カラム5000件のデータで実行時20MB強ぐらいのメモリ使用量でした。

    この最小限のコードなら問題ない、という場合は、ひとつずつ機能を足していって確認することで
    どこが問題になっていそうかわかると思います。

    > 実害といえば、動作が非常に緩慢になります。
    > あと、実害というほどではないのですが、CPUのファン(?)の音が、思い切り回り続けます。
    > きっと何か無駄な処理をしてしまっているのだと思っています。

    CPU100%で無限ループしている時とか、そんなふうになりますね。

    2012年5月22日 2:32