【解決は500~1000ポイント:C#のプロセス間処理】

AプロセスとBackgroundWorkerが存在します。
AプロセスはBackgroundWorkerにプログレスバー表示を依頼し、
その後、処理が完了した段階でBackgroundWorkerにプログレスバー終了依頼を出します。
このとき、
Aプロセスでは以下のようにプログレスバーの終了を待ち受けます。


 while(プログレスバー表示フラグ==true){
  System.Windows.Forms.Application.DoEvents();
  log.write("注意:プログレスバー表示中");
 }
 
BackgroundWorkerでBoolean型のプログレスバー表示フラグをfalseに
しているのですが、Aプロセスのループから抜けません。
BackgroundWorkerが終了したことを伝えループを終える良い解決方法はありませんか?

回答の条件
  • 1人2回まで
  • 登録:
  • 終了:2011/04/29 18:24:28
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:heke2mee No.4

回答回数162ベストアンサー獲得回数43

ポイント1000pt

サンプルを作ってみました。

フォーム上にプログレスバーとボタンを配置してください

プログレスバー progressBar1

ボタン button1


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace TestThreading
{
    public partial class Form1 : Form
    {
        private BackgroundWorker bgWorker;
        delegate void dlgProgress(int percentage);
        bool wait = false;

        public Form1()
        {
            InitializeComponent();
            bgWorker = new BackgroundWorker();
            bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
            bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
        }



        private void button1_Click(object sender, EventArgs e)
        {
            this.progressBar1.Value = 0;
            this.button1.Enabled = false;

            bgWorker.RunWorkerAsync();

            wait = true;
            while (wait)
            {
                Application.DoEvents();
            }
            this.button1.Enabled = true;
        }

        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //時間のかかる処理
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(100);
                int percentage = 10 * (i + 1);

                progressBarUpdate(percentage);
            }
        }

        void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            wait = false;
        }


        /// <summary>
        /// プログレスバーの更新
        /// </summary>
        public void progressBarUpdate(int percentage)
        {
            if (!(this.IsHandleCreated))
            {
                return;
            }
            MethodInvoker process =
                (MethodInvoker)delegate()
                {
                    if (this.progressBar1.Maximum > percentage)
                        this.progressBar1.Value = percentage;
                    else
                        this.progressBar1.Value = 100;
                };
            if (this.InvokeRequired)
            {
                this.Invoke(process);
            }
            else
            {
                process.Invoke();
            }

         }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            bgWorker.DoWork -= new DoWorkEventHandler(bgWorker_DoWork);
            bgWorker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);

            bgWorker.Dispose();
            bgWorker = null;
        }
    }

}

id:harunoharuno

回答ありがとうございます。

回答いただいたサンプルソースは大体私のソースと類似しています。

残念ながらまだ問題は解決していませんが、解ったことがございましたので

追記します。


Aプロセスで以下のようにコーディングした場合


  this.progressBar1.Value = 0;

  this.button1.Enabled = false;

  bgWorker.RunWorkerAsync();

  wait = true;

  while (wait)

  {

    log.Write("test") ←①

    Application.DoEvents();

  }


①で出力しているログが1回しか記録されません。

Application.DoEventsイベント後に、While文が動いていないように

思えるのですが、いまだ原因は不明です。


なお、現状のコーディングでも10回操作すれば8回ほど

期待通りの動作をすることを確認しています。

たまに、

ループのフラグがFalseになったのにも関わらず抜け出さないことがあるようです。

ご助言宜しくお願いします。

2011/04/28 18:14:54

その他の回答3件)

id:taknt No.1

回答回数13539ベストアンサー獲得回数1198

ポイント100pt

グローバルの変数で プログレスバーが 終了したら フラグを 立てるようにする。

その変数を 質問の中のループで チェックして フラグが たってたら 抜けるようにしてやればいいでしょう。


または、そのループ内で プログレスバーが終了したか どうか(たぶん 100%)かどうかを

チェックして 終了したら 抜ければいいでしょう。

id:harunoharuno

回答ありがとうございます。

現在、プログレスバーを終了するタイミングでBoolean型のプログレスバー表示フラグをfalseに

しています。このプログレスバー表示フラグはAプロセスが管理する変数なので

ともにアクセスできるグローバルなエリアにフラグを設けて確かめます。

2011/04/27 23:17:13
id:halohalolin No.2

回答回数47ベストアンサー獲得回数3

ポイント100pt

処理は、Aプロセスで行っているのでしょうか、それともBackgroundWorkerで行っているのでしょうか?

私ならAプロセスの中に処理を組み込み、BackgroundWorkerはプログレスバーの表示に集中させます。

そしてAプロセスが終わり次第、AプロセスからBackgroundWorkerには表示を終了するよう指示させます。

つまり

 while(プログレスバー表示フラグ==true){

  System.Windows.Forms.Application.DoEvents();

  log.write("注意:プログレスバー表示中");

  処理

  if(処理終了==true){ プログレスバー表示フラグ=false; }

 }

 プログレスバーの後片付け

という感じです。

id:harunoharuno

回答ありがとうございます。

>>処理は、Aプロセスで行っているのでしょうか、それともBackgroundWorkerで行っているのでしょうか?

>>私ならAプロセスの中に処理を組み込み、BackgroundWorkerはプログレスバーの表示に集中させます。

>>そしてAプロセスが終わり次第、AプロセスからBackgroundWorkerには表示を終了するよう指示させます。


現状次のようになっています。

●Aプロセスの処理

 プログレスバー終了フラグ=true;    ←②

 while(プログレスバー表示フラグ==true){

  System.Windows.Forms.Application.DoEvents();

  log.write("注意:プログレスバー表示中");←③

 }

 log.write("処理完了");←⑥



●BackgroundWorkerの処理



 while(プログレスバー終了フラグ){

  log.write("進捗表示");←①

 }

 プログレスバー終了←④

 プログレスバー表示フラグ=true;←⑤

 System.Windows.Forms.Application.DoEvents();


1~6の順序で処理が流れてほしいのですが、③のループから抜け出せません。

この原因についてもご回答いただきたいです。

2011/04/27 23:30:26
id:Galapagos No.3

回答回数963ベストアンサー獲得回数89

ポイント50pt

ご存じかと思いますが、Windowsの場合、プロセス間の値のやり取りはレジストリを介して行います。

その「プログレスバー表示フラグ」というのはレジストリ変数になっていますか?

ご確認ください。


参考 「レジストリの値を取得するには?

id:heke2mee No.4

回答回数162ベストアンサー獲得回数43ここでベストアンサー

ポイント1000pt

サンプルを作ってみました。

フォーム上にプログレスバーとボタンを配置してください

プログレスバー progressBar1

ボタン button1


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace TestThreading
{
    public partial class Form1 : Form
    {
        private BackgroundWorker bgWorker;
        delegate void dlgProgress(int percentage);
        bool wait = false;

        public Form1()
        {
            InitializeComponent();
            bgWorker = new BackgroundWorker();
            bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
            bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
        }



        private void button1_Click(object sender, EventArgs e)
        {
            this.progressBar1.Value = 0;
            this.button1.Enabled = false;

            bgWorker.RunWorkerAsync();

            wait = true;
            while (wait)
            {
                Application.DoEvents();
            }
            this.button1.Enabled = true;
        }

        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //時間のかかる処理
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(100);
                int percentage = 10 * (i + 1);

                progressBarUpdate(percentage);
            }
        }

        void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            wait = false;
        }


        /// <summary>
        /// プログレスバーの更新
        /// </summary>
        public void progressBarUpdate(int percentage)
        {
            if (!(this.IsHandleCreated))
            {
                return;
            }
            MethodInvoker process =
                (MethodInvoker)delegate()
                {
                    if (this.progressBar1.Maximum > percentage)
                        this.progressBar1.Value = percentage;
                    else
                        this.progressBar1.Value = 100;
                };
            if (this.InvokeRequired)
            {
                this.Invoke(process);
            }
            else
            {
                process.Invoke();
            }

         }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            bgWorker.DoWork -= new DoWorkEventHandler(bgWorker_DoWork);
            bgWorker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);

            bgWorker.Dispose();
            bgWorker = null;
        }
    }

}

id:harunoharuno

回答ありがとうございます。

回答いただいたサンプルソースは大体私のソースと類似しています。

残念ながらまだ問題は解決していませんが、解ったことがございましたので

追記します。


Aプロセスで以下のようにコーディングした場合


  this.progressBar1.Value = 0;

  this.button1.Enabled = false;

  bgWorker.RunWorkerAsync();

  wait = true;

  while (wait)

  {

    log.Write("test") ←①

    Application.DoEvents();

  }


①で出力しているログが1回しか記録されません。

Application.DoEventsイベント後に、While文が動いていないように

思えるのですが、いまだ原因は不明です。


なお、現状のコーディングでも10回操作すれば8回ほど

期待通りの動作をすることを確認しています。

たまに、

ループのフラグがFalseになったのにも関わらず抜け出さないことがあるようです。

ご助言宜しくお願いします。

2011/04/28 18:14:54
  • id:harunoharuno
    捕捉:AプロセスからBackgroundWorker.joinを呼び出し、
    終了まで待ち受ける方法を使えればありがたいのですが、
    BackgroundWorkerでAプロセスの資源にアクセスする都合上Invokeしており、
    BackgroundWorker.joinを使うと、Invokeによる
    Aプロセスの呼び出しでデッドロックしてしまいます。

    Invokeしている理由は、
    プログレスバー終了の際に、「有効ではないスレッド間の操作:コントロールが作成されたスレッド以外のスレッドから
    コントロール"ProgressDlg"がアクセスされました。」と
    表示されるためです。Invokeを用いAプロセスから処理を行った場合、正常に動作します。
  • id:harunoharuno
    回答いただきありがとうございました。
    問題が解決しましたので質問の回答受付を停止します。
  • id:harunoharuno
    解決したと思いましたが勘違いでした。

    というのは

    >>Aプロセスのループから抜けません。

    について抜けるケースと抜けないケースがあるようです。
    100パーセント成功しないので回答の受付を再開します。
  • id:harunoharuno
    [追記]
    AプロセスはFormに関連付いたプロセスなのですが、DataGridViewに
    イベントを横取りされて、プログレスバー終了待ちうけのループから
    処理が外れているような気がします。
  • id:heke2mee
    log.writeってもしかしてファイルにログを出力していませんか?
    処理が重くてループ回数が減ってる可能性はありませんか?

    下記のように変更してもらえばループが回っているのを確認できると思います。

    private void button1_Click(object sender, EventArgs e)
    {
    this.progressBar1.Value = 0;
    this.button1.Enabled = false;

    bgWorker.RunWorkerAsync();

    wait = true;
    int count = 0;
    while (wait)
    {
    count++;
    System.Diagnostics.Debug.WriteLine("loop" + count);//デバッグの出力windowに表示
    Application.DoEvents();
    }
    System.Diagnostics.Debug.WriteLine("loop end");

    this.button1.Enabled = true;
    }

    また、Application.DoEvents() はメッセージキューにたまっているイベントを実行します。
    したがって、サンプルソースにボタンを1つ追加し次のコードを追加すると
    while (wait)のループでもボタンのクリックイベントが実行され
    メッセージボックスが表示されます。

    private void button2_Click(object sender, EventArgs e)
    {
    MessageBox.Show("xxx");
    }

    Application.DoEvents()を消した場合はメインプロセスがループ内で
    ずっとビジー状態なので他プロセスからInvokeしてもデッドロック状態になります。


    これを避けるにはループに入る前でイベントを受け付けたくないコントロールを
    Enabledをfalseにする必要があります。あとWindowのクローズなども
    できなくする。(ループを抜けたあとにもとに戻す)

    ただこのようにすると、ループで待たせなくても結果は同じです。
    EnabledをTrueにするタイミングがbgWorker_RunWorkerCompletedを受けたときに
    変わるだけです。


    コントロールを操作させないようにするのは駄目なのでしょうか?
  • id:harunoharuno
    再度の回答ありがとうございます。

    >>log.writeってもしかしてファイルにログを出力していませんか?
    >>処理が重くてループ回数が減ってる可能性はありませんか?

    出力しています。


    そしてご指摘の通り・・・
    成功しているときは何回かループしているのですが、、、、
    失敗している場合ループ回数が減っています。


    <成功しているログ>


    注意:プログレスバー表示中
    注意:プログレスバー表示中
    注意:プログレスバー表示中
    注意:プログレスバー表示中
    プログレスバー表示フラグ=true
    処理完了

    <失敗しているログ>

    注意:プログレスバー表示中
    データグリッドビューを派生させたクラスのログ←●
    プログレスバー表示フラグ=true


    となっています。
    全てのコントロールのEnabledをFalseに設定しているのですが、
    失敗しているときは必ず
    データグリッドビュー関連のログ(●部分)をはいています。

    このことから
    私は、現在プログレスバーの待ち受けのループを、DataGridView関連の
    コントロールに横取りされているのではないかと思っているのですが、、、検討ちがいでしょうか?



    >>また、Application.DoEvents() はメッセージキューにたまっているイベントを実行します。
    >>したがって、サンプルソースにボタンを1つ追加し次のコードを追加すると
    >>while (wait)のループでもボタンのクリックイベントが実行され
    >>メッセージボックスが表示されます。

    >>private void button2_Click(object sender, EventArgs e)
    >>{
    >>MessageBox.Show("xxx");
    >>}


    button2がクリックされたとしても
    ループの処理は継続し続けるんですよね?

    私のログの中でループが突然終わる現象を確認しているためなんだか訳がわからなくなってきました。

  • id:heke2mee
    GUI コントロールにバインドされている場合、DataSet/DataTable/DataRow の変更も GUI と同一スレッド上で行う必要があります。
    という情報をこちらで見つけたのですが、そのあたりは大丈夫ですか
    http://dobon.net/vb/bbs/log3-26/15520.html
  • id:harunoharuno

    >>GUI コントロールにバインドされている場合、DataSet/DataTable/DataRow の変更も GUI と同一スレッド上で行う必要があります。
    >>という情報をこちらで見つけたのですが、そのあたりは大丈夫ですか
    >>http://dobon.net/vb/bbs/log3-26/15520.html


    追加回答ありがとうございます。

    Aプロセスについてですが
    より具体的に申しますとForm1.csと同じようにフォームに紐づくものです。


    Form1.csのコンストラクタでデータを取得し、DataGridViewの
    初期表示を設定しているので、同一スレッドで処理が完結し、
    以降は表示のみとなります。

  • id:harunoharuno
    いったんこの質問は終了します。
    質問内容を検討しもう一度質問しますので見かけたらよろしくお願いします。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません