みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

別スレッドとイベントの終了手順について

C#の質問になります。
メインフォーム上で別スレッドを起動し、別スレッドからのイベントで
メインフォーム上のテキストボックスにメッセージを表示しています。

サンプルソースはフォームにボタン2個とテキストボックス1個を貼り
付けたものになり、ボタン1でスレッド起動、ボタン2で停止させてい
ます。

正常パターンでボタン1とボタン2を交互に押下すると意図したとおり
テキストボックスにメッセージが出力されます。

このプログラムで、ボタン1を押下し別スレッドが起動した状態で、
フォームの×ボタンを押下すると別スレッドの停止処理中にJoin()
呼び出しで永久に止まってしまいます。
止めるべきスレッド中でイベント(OnTraceEvent)を呼び出している
のが問題のような気がします。(この処理がなければ正常)

このような時の終了手順の王道的なものはありますでしょうか。



  public partial class Form1 : Form
  {
    ThreadTest _thread = null;

    public Form1()
    {
      InitializeComponent();
    }

    private void button1_Click( object sender, EventArgs e )
    {
      if ( this._thread == null )
      {
        this._thread = new ThreadTest();
        this._thread.TraceEvent += new ThreadTest.TraceEventHandler( OnTrace );
        this._thread.Open();
      }
    }

    private void button2_Click( object sender, EventArgs e )
    {
      if ( this._thread != null )
      {
        this._thread.Close();
        this._thread.TraceEvent -= new ThreadTest.TraceEventHandler( OnTrace );
        this._thread = null;
      }
    }

    private void OnTrace(String message)
    {
      if ( this.IsHandleCreated == false )
      {
        return;
      }

      MethodInvoker process = (MethodInvoker)delegate()
      {
        textBox1.AppendText( message + "\r\n" );
      };

      if ( this.InvokeRequired )
      {
        this.Invoke( process );
      }
      else
      {
        process.Invoke();
      }

      return;
    }

    private void Form1_FormClosed( object sender, FormClosedEventArgs e )
    {
      //フォームの×ボタンを押下した時にスレッドを停止しないと
      //破棄されたコントロールを操作しようとするため下記を追加
      if ( this._thread != null )
      {
        this._thread.Close();
        this._thread.TraceEvent -= new ThreadTest.TraceEventHandler( OnTrace );
        this._thread = null;
      }
    }
  }

  class ThreadTest
  {
    public delegate void TraceEventHandler( String message );
    public event TraceEventHandler TraceEvent;
    protected virtual void OnTraceEvent( String message )
    {
      TraceEventHandler TraceEventTemp = TraceEvent;
      if ( TraceEventTemp != null )
      {
        TraceEventTemp( message );
      }
    }

    private Thread _threadLoop = null;
    private volatile Boolean _threadFlag = false;

    public void Open()
    {
      //スレッド開始
      if ( this._threadLoop == null )
      {
        this._threadLoop = new Thread( new ThreadStart( Loop ) );
        this._threadLoop.Start();
        while ( !this._threadLoop.IsAlive ) ;
      }
    }

    public void Close()
    {
      //スレッド停止
      if ( this._threadLoop != null )
      {
        this._threadFlag = false;
        //this._threadLoop.Abort();//ここを有効にすればとりあえず終了する
        this._threadLoop.Join();
        this._threadLoop = null;
      }
    }

    public void Loop()
    {
      this._threadFlag = true;
      while ( this._threadFlag )
      {
        OnTraceEvent( DateTime.Now.ToString( "yyyy/MM/dd hh:mm:ss:fff" ) );
        Thread.Sleep( 100 );
      }
    }
  }

投稿日時 - 2012-04-19 14:51:24

QNo.7429618

困ってます

質問者が選んだベストアンサー

暇だったので検証してみたところ、デバッガで見る限りですが、
Closed(Closingの時点で既に)発生の段階で、this.Invoke(process)が硬直しているように思います。

調べてみるとこれは割と有名なことらしく、
UIのスレッド側が既に終了遷移しているためにInvokeを受けることができない状況であることが原因のようです。
条件をつけたりして細工をしたとしても低確率で再現してしまうようです。

対策としては、
・スレッドとクラスを完全に分離する(Invokeを使用しない)。
・BeginInvoke(非同期実行)で実装する。
・Closing発生時にLoopスレッドをAbortする。
くらいでしょうか。

投稿日時 - 2012-04-20 15:15:57

補足

検証、ありがとうございます。
  this.Invoke( process );

  this.BeginInvoke( process );
にすることでフォームの×ボタン押下でも終了するようになりました。
非同期にする事で最後の問題となるInvokeがどのように処理されているのか
気にはなるところですが、動作としてはエラーもなく正常に終了しましたの
で、今回の対策で様子を見たいと思います。

ちなみに、Trhred.Abort();でも終了できました。

投稿日時 - 2012-04-20 18:23:51

お礼

すみません、補足で書いてしまい、お礼を忘れておりました。
改めてお礼とさせて頂きます。
ありがとうございました。

投稿日時 - 2012-04-28 10:41:39

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(2)

ANo.1

C#はよく知らないのですが、スレッドの終わらせ方としては、そんな感じで普通かなと思いました。
ちなみにこれ、×ボタンで閉じた時だけ起こりますか?
タイミングによっては、ボタン2を押した時にも起こりそうな気がします。

予想ですが、Invokeという関数は、UI側のスレッドの実行が完了するまで、
制御が戻らない、同期型の動作をする関数ではないでしょうか?

もしそうだとすると、UIスレッドがJoinでワーカースレッドの完了を待ち、
ワーカースレッドは、UIスレッドがテキストを更新するのを待ち、
というデッドロック状態になっているのではないかと思います。

Invokeが同期型なら、それの非同期版の関数が用意されているような気もしますが、
そういうものがあるかどうかはわからないので、その点については回答を控えます。

投稿日時 - 2012-04-20 14:41:30

お礼

情報ありがとうございます。
今回は非同期型のBeginInvoke()で対応しましたが、デッドロックが
原因であれば他の方法もあるかもしれませんので、もう少し検証して
みたいと思います。

投稿日時 - 2012-04-20 18:27:54

あなたにオススメの質問