- PR -

System.Threading.Timer の終了処理について

投稿者投稿内容
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-05-07 01:48
初投稿です。C++/CLI で WindowsForm アプリを作成しています。
System::Threading::Timer を使用し、定期タイミング毎にFormに対しInvalidate()をかけ、描画を更新しています。上記タイマーを用いる場合、タイマーコールバックとしてデリゲートしたメソッドは、別スレッドとしてキックされるはずなので、タイマーコールバック関数(フォームのメソッドをデリゲート)では以下のように、Invokeを使ってFormの更新をかけています。

delegate void RenderDelegate( void );
void MyTimerCB( Object^ state ){
 this->Invoke( gcnew RenderDelegate( this, &Form::Invalidate ) );
}
起動中の動作としては何ら問題ないのですが、フォームを閉じる(右上のシステムアイコンの×ボタン押下時)と、System.ObjectDisposedExceptionが出て、落ちてしまいます。終了時にタイマーのDispose が必要なのだなと思い、Form のデストラクタや、FormのClosed() イベントハンドラに、タイマーのDispose処理を記述してみたのですが結果は同じです。
ただ不思議なことに、「タイマー終了」というボタンを設け、そのコールバックの中でタイマーをDispose するようにし、アプリ起動→「タイマー終了」ボタン押下という操作を行うと、その後フォームを閉じても問題なくアプリは正常終了します。(ちなみに、C++/CLIなので上記Disposeの記述としては、実際には~Timer() を呼び出しています。)

Form が閉じられた際にタイマーのDisposeを記述する場所やタイミングに問題があるのかと考えているのですが、どこに記述すればよいのかがわかりません。なお、上記を組むにあたり、
http://www.atmarkit.co.jp/fdotnet/dotnettips/373threadtimer/threadtimer.html
などを参考にしています。もしわかる方がいらっしゃいましたらば、ご教示頂ければと思います。
以上、よろしくお願いします。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2007-05-07 02:30
ためしに、何もないFormで
コード:
        System::Threading::Timer^ t;


private: System::Void Form1_Load(System:bject^ sender, System::EventArgs^ e) {
this->t = gcnew System::Threading::Timer(
gcnew System::Threading::TimerCallback(this, &Form1::MyTimerCB), nullptr,0, 1000);
}
private:
delegate void RenderDelegate( void );
void MyTimerCB(Object^ state)
{
this->Invoke(gcnew RenderDelegate(this, &Form::Invalidate));
}


というコードを記述して×ボタンで終了させてもExceptionが発生しませんでした。

>ObjectDisposedException
はおそらく、Formが破棄されているにもかかわらず、Form::Invalidateの処理を
しようとして起こっているものでしょう。

>FormのClosed()
VS2005の場合はFormClosedイベントを使うのが一般的かと。
>実際には~Timer() を呼び出しています。
deleteを使うのでは?

コード:
    private:

System::Void Form1_FormClosed(System:bject^ sender, System::Windows::Forms::FormClosedEventArgs^ e)
{
this->t->Change(System::Threading::Timeout::Infinite, System::Threading::Timeout::Infinite);
delete this->t;
this->t = nullptr;
}


とするとどうでしょうか?

Timer.Change メソッド (Int32, Int32)
http://msdn2.microsoft.com/ja-jp/library/yz1c7148(VS.80).aspx

[ メッセージ編集済み 編集者: Blue 編集日時 2007-05-07 02:32 ]
やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-05-07 07:01
早速ご返信を頂けありがとうございます。
たしかにBlueさんに実験して頂いたように、インターバルが1000だと確かに何も問題なく正常終了します。
ところがインターバル値を 100 とか、それより小さい値にすると現象が現れます。(当方はインターバル値33で使いたいものです。)お手数ですみませんが、Blueさんの実験して頂いたコードがもし残ってましたらば、インターバル値が100や、100以下でも問題なく正常終了するかどうか、教えて頂けないでしょうか?

当方でも何もないFormにて、Form1Load() のタイマー起動のコードと、Form1_FormClosed()の終了コードともに、Blueさんのものと寸分たがわぬものを作成してみて実験してみましたが、結果は同じでした。(インターバル1000だと正常終了、100 以下だと現象発生。)

>>ObjectDisposedException
>はおそらく、Formが破棄されているにもかかわらず、Form::Invalidateの処理を
>しようとして起こっているものでしょう。

私もそう思います。Blueさんにやって頂いたようにChangeメソッドを使って、念のためタイマーを止めておくことも以前にも試しましたが、短いインターバル値だと、やはり現象が出てしまいます。
いろいろと邪推し、タイマーによって作成されたThreadPoolのスレッドを全て止めて消すみたいなことをしなくてはならないのかと調べたりしているのですが、よくわかりません。

[ メッセージ編集済み 編集者: やっぷ 編集日時 2007-05-07 07:08 ]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2007-05-07 07:05
引用:
(ちなみに、C++/CLIなので上記Disposeの記述としては、実際には~Timer() を呼び出しています。)

disposeとデストラクタ、ファイナライザは別のものです。
ファイナライザを呼んでも、必ずdisposeされるわけではありません。
_________________
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2007-05-07 09:10
単にスレッド間で競合が発生してるだけでは?
# タイマは急に止まれない

タイマ破棄済みかを確認してから処理するようにした方がいいでしょう。
--追記
っとと、それでも確実には出来ないので、最悪例外をキャッチしてやり過ごす必要はあるかな?


[ メッセージ編集済み 編集者: なちゃ 編集日時 2007-05-07 09:20 ]
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2007-05-07 20:43
引用:
お手数ですみませんが、Blueさんの実験して頂いたコードがもし残ってましたらば、インターバル値が100や、100以下でも問題なく正常終了するかどうか、教えて頂けないでしょうか?


Exception発生しますね。
(というか、最初から書いてもらえればよかったのだが。)

なちゃさんのおっしゃるとおり、タイマー破棄済みかどうかのチェックを入れてみたのですが、
例外が出てしまうようです。
例外をcatchして握りつぶしても問題なさそうなら、そうするしかないのかなぁ。
コード:
        void MyTimerCB(Object^ state)
        {
            if (this->t) // タイマーが破棄済みか
            {
                try
                {
                    this->Invoke(gcnew RenderDelegate(this, &Form::Invalidate)); 
                }
                catch (System::Exception^) {}
            }
        }


やっぷ
会議室デビュー日: 2007/05/07
投稿数: 17
投稿日時: 2007-05-07 21:05
Jittaさん、なちゃさん、Blueさん
どうもありがとうございました。
たしかに例外キャッチして握りつぶせば文字通り握りつぶすことはできそうですが…。^^;
ちなみに、サイドスレッドのタイマーコールバックにて、
Invokeではなく、this->Invalidate() を直接呼ぶようにすると、動作上何も問題はなく、
また例外も発生しないのですが、これって正しいソリューションなのでしょうかね?^^;
void timerCB( Object^ s )
{
 // Invoke( gcnew RenderDelegate( this, &Form1::Invalidate ) );
 this->Form1::Invalidate();
 return;
}
当方経験が浅く、別スレからフォームの更新を直接行ってはいけないというのを、
まだ実際に体験したわけではなく、「言い伝え」としてしか認識していないもので…。
やはりこれはご法度なのでしょうか。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-05-07 21:18
こんばんは。

引用:

やっぷさんの書き込み (2007-05-07 01:48) より:
初投稿です。C++/CLI で WindowsForm アプリを作成しています。
System::Threading::Timer を使用し、定期タイミング毎にFormに対しInvalidate()をかけ、描画を更新しています。上記タイマーを用いる場合、タイマーコールバックとしてデリゲートしたメソッドは、別スレッドとしてキックされるはずなので、タイマーコールバック関数(フォームのメソッドをデリゲート)では以下のように、Invokeを使ってFormの更新をかけています。



いまさら言うのもなんですが、なんでSystem.Threading.Timer を使われるのでしょう?
実際試していないので憶測ですが、
御提示のコードではウィンドウタイマー(System.Windows.Forms.Timer)よりもパフォーマンスがよいとは思えなのですが。

スキルアップ/キャリアアップ(JOB@IT)