C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[9]: DispatcherTimerのコールバックが動かなくなる


(過去ログ 109 を表示中)

[トピック内 13 記事 (1 - 13 表示)]  << 0 >>

■64995 / inTopicNo.1)  DispatcherTimerのコールバックが動かなくなる
  
□投稿者/ もびお (1回)-(2013/01/29(Tue) 00:55:47)

分類:[.NET 全般] 

C#でDispatcherTimerを使用した場合について質問させて頂きます。

DispatcherTimerを作っているクラスを生成(new)し直すと、コールバック関数(Tickイベント)が呼ばれなくなってしまいます。
クラスを生成した時のコンストラクタでDispatcherTimerも作り直し、その後Startしているのですが呼ばれません。
Start直前にブレークポイントを置いて確認してもおかしい部分はありません(IsEnabledもtrue)。

ちなみに、DispatcherTimerを持つクラスを生成し直さなければ問題無く動きます。

もし、DispatcherTimerで上記のような使い方ができない場合、他に代用可能なタイマーはないでしょうか?
WPFを使用しているので、UIスレッドにアクセス可能なら良いのですが・・・。

どなたか教えて頂ければ幸いです。

▼使用しているDispatcherTimer
http://msdn.microsoft.com/ja-jp/library/system.windows.threading.dispatchertimer.aspx
引用返信 編集キー/
■64998 / inTopicNo.2)  Re[1]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ とっちゃん (38回)-(2013/01/29(Tue) 10:40:11)
とっちゃん さんの Web サイト
No64995 (もびお さん) に返信
> DispatcherTimerを作っているクラスを生成(new)し直すと、コールバック関数(Tickイベント)が呼ばれなくなってしまいます。
> クラスを生成した時のコンストラクタでDispatcherTimerも作り直し、その後Startしているのですが呼ばれません。
> Start直前にブレークポイントを置いて確認してもおかしい部分はありません(IsEnabledもtrue)。
>
生成し直し?

通常、タイマーオブジェクト(DispatcherTimerに限らない)は、一度作ったらずーっとそのまま使えます。
タイマーで通知を受け取った後、しばらく使わない(次の発行タイミングが確定するまで停止する)のであれば、
Stop()メソッドを呼び出して止めておき、再びタイマーを発生させるときに Start() すればよいと思いますが?



ところで、生成しなおしている部分というのは具体的にどういうコードを書いていますか?

端的に書くと

timer = new DispatcherTimer();
// ここにあるはずのイベントハンドラの登録がない( timer.Tick += ... )
timer.Interval = new TimeSpan(...);
timer.Start();

という形で、イベントハンドラの再登録が抜けていませんか?
タイマーに限らず、インスタンスオブジェクトへのハンドラ登録は、オブジェクトごとに行う必要があります。
最初に生成したオブジェクトのインスタンスと、生成しなおしたときのオブジェクトのインスタンスは異なるものです。
ですので、それぞれのインスタンスにハンドラ登録が必要になります。

そのあたりをチェックしなおしてみてはどうでしょう?
引用返信 編集キー/
■65000 / inTopicNo.3)  Re[2]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ もびお (2回)-(2013/01/29(Tue) 11:51:18)
回答ありがとうございます。

> 生成し直し?
> 
> 通常、タイマーオブジェクト(DispatcherTimerに限らない)は、一度作ったらずーっとそのまま使えます。
> タイマーで通知を受け取った後、しばらく使わない(次の発行タイミングが確定するまで停止する)のであれば、
> Stop()メソッドを呼び出して止めておき、再びタイマーを発生させるときに Start() すればよいと思いますが?
申し訳ありません。説明が足りませんでした。
以下の理由によりタイマーオブジェクトを作り直しています。
・基底クラスと継承クラスを作成しており、タイマーオブジェクトは基底クラスに定義している
・継承しているクラスが複数あり、使用するクラスが変更されることがあるため継承クラスを生成し直すことがある。
・タイマーオブジェクトは継承クラスのコンストラクタでnewしている。
タイマーオブジェクトを保持するクラスを生成しなおすため、結果的にタイマーオブジェクトも再生成されているといった具合です。


> ところで、生成しなおしている部分というのは具体的にどういうコードを書いていますか?
以下のように記述しています。

base._timer =
                new DispatcherTimer(TimeSpan.FromMilliseconds(1000),
                DispatcherPriority.Send, new EventHandler(base.timerCallback),
                Dispatcher.CurrentDispatcher);
base._timer.IsEnabled = false;
base._timer.Stop();
                
保持しているクラスを生成し直さなければ動作するので、記述自体は合っている気がします・・・。
ちなみに、newした後は別の処理を行い、最後に以下も記述しています。

_timer.Interval = TimeSpan.FromMilliseconds(1000);
_timer.IsEnabled = true;
_timer.Start();

クラスの再生成をし直す前のDispatcherTimerが残っていたりするのでしょうか?
普通のTimerと違い破棄する命令が無いのが気になるところです・・・。

引用返信 編集キー/
■65002 / inTopicNo.4)  Re[3]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ howling (166回)-(2013/01/29(Tue) 12:56:23)
No65000 (もびお さん) に返信
普段C#のTimerクラスしか使っていないので、気になってソース組んでみました。
なんとなく理由はわかったかなぁ…2重に動く、ということでは?
コールバックが呼ばれなくなる、ということは無かったです。
元のTimerのTickイベントの解除をしないと、作り直しても裏で存在しているっぽいですね。
ここらへん、どうなんでしょうか?>ALL

いずれにしても、とっちゃんさんのおっしゃる通り、なぜ作り直すのかわからないです。
基本は、Intervalだけ変えてスタートストップ、という流れはC#のTimerクラスと何ら変わりないようです。
(Enableは自分で変えるものじゃない認識でいます。Startすると勝手にtrueになり、Stopでfalseにしてくれるので)

以下サンプルコードです。
FormにButton1,Button2,Label1を貼り付けておけば、下記のコードで通るはずです。
名前空間とかはお願いしますね…。
引用返信 編集キー/
■65003 / inTopicNo.5)  Re[4]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ howling (167回)-(2013/01/29(Tue) 12:59:05)
2013/01/29(Tue) 12:59:27 編集(投稿者)
    //継承クラス
    public class ExpandClass : BaseClass
    {
        public ExpandClass()
        {
            clsTimer =
                new DispatcherTimer(TimeSpan.FromMilliseconds(1000),
                DispatcherPriority.Send, new EventHandler(base.dispatcherTimer_Tick),
                Dispatcher.CurrentDispatcher);
            clsTimer.Stop();
            iCounter = 0;
        }

        //タイマー作り直し
        public void RemakeTimer()
        {
            //これを消すと多重に動く
            clsTimer.Tick -= new EventHandler(base.dispatcherTimer_Tick);
            //再作成
            clsTimer =
                new DispatcherTimer(TimeSpan.FromMilliseconds(1000),
                DispatcherPriority.Send, new EventHandler(base.dispatcherTimer_Tick),
                Dispatcher.CurrentDispatcher);
            clsTimer.Stop();
            iCounter = 0;
        }

        //タイマー開始
        public void StartTimer()
        {
            if (clsTimer.IsEnabled == false)
            {
                clsTimer.Interval = TimeSpan.FromMilliseconds(1000);
                clsTimer.Start();
            }
        }

        //タイマー終了
        public void StopTimer()
        {
            if (clsTimer.IsEnabled == true)
            {                
                clsTimer.Stop();
            }
        }

        //タイマーが実行中かどうか
        public bool IsTimerStarted()
        {
            return clsTimer.IsEnabled;
        }
    }

    //
    public class BaseClass
    {
        protected DispatcherTimer clsTimer = null;
        protected int iCounter = 0;
        private Label clsBindLabel = null;

        //ラベルにカウンタ表示
        protected void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            iCounter++;
            if(clsBindLabel != null)
            {
                clsBindLabel.Content = iCounter.ToString();
            }
        }

        public void SetLabel(Label clsLabel)
        {
            clsBindLabel = clsLabel;
        }
    }

引用返信 編集キー/
■65004 / inTopicNo.6)  Re[5]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ howling (168回)-(2013/01/29(Tue) 13:03:39)
Formを投稿すると迷惑投稿者扱いになる…

継承クラスを作成して、
継承クラスのSetLabelにLabel1を割り当てて、
Start/Stopすると動きます。(Button1でやってました)
RemakeTimerを呼んで、もう1度SetLabelでLabel1を割り当てると再作成の意味合いです。

…困ったもんだ。
引用返信 編集キー/
■65014 / inTopicNo.7)  Re[3]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ とっちゃん (39回)-(2013/01/29(Tue) 17:57:23)
とっちゃん さんの Web サイト
No65000 (もびお さん) に返信

> 以下の理由によりタイマーオブジェクトを作り直しています。
> ・基底クラスと継承クラスを作成しており、タイマーオブジェクトは基底クラスに定義している
> ・継承しているクラスが複数あり、使用するクラスが変更されることがあるため継承クラスを生成し直すことがある。
> ・タイマーオブジェクトは継承クラスのコンストラクタでnewしている。
> タイマーオブジェクトを保持するクラスを生成しなおすため、結果的にタイマーオブジェクトも再生成されているといった具合です。

えっと。。。生成しなおすのは、継承クラスですか?
それとも、基底クラスが保持しているタイマーオブジェクトですか?

いずれにしても、生成しなおすというのは、別のインスタンスを用意するということですよね?
それが、タイマーオブジェクトだけを指しているのか、それを抱えるクラスそのものをさしているのかがよくわかりませんが。。。

とりあえず、タイマーオブジェクトをメインウィンドウのメンバー変数に用意して

コンストラクタで、
timer = new DispatcherTimer( TimeSpan.FromMilliseconds( 1000 ), DispatcherPriority.Send, timerCallback, Dispatcher.CurrentDispatcher );
timer.Stop(); // ※

timer = new DispatcherTimer( TimeSpan.FromMilliseconds( 1000 ), DispatcherPriority.Send, timerCallback2, Dispatcher.CurrentDispatcher );

InitializeComponent();

timer.Interval = TimeSpan.FromMilliseconds( 1000 );
timer.Start();

としてみました。

この場合は、正しくtimerCallback2 だけが呼び出されました。
※の行を呼び出さないと、timerCallback と timerCallback2 の両方が呼び出されました。
ちなみに、環境は、Win8,VS2012(.NET 4.5 のWPFアプリ。C#で記述)です。

ということなので、呼ばれないということはありませんでした。
どこか別のところにバグがあるか、意図しない呼び出され方をしているかのどちらかでしょう。

引用返信 編集キー/
■65015 / inTopicNo.8)  Re[4]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ howling (171回)-(2013/01/29(Tue) 18:00:55)
No65014 (とっちゃん さん) に返信
やっぱりそうですよね。
StopかTickイベントの解除を行わないと、裏で動いたまんま触れなくなる(senderで取ればいけますから嘘か)感じだなぁと。
それでも、2重に動くことはあっても、動かなくなることは無いですよね。
どうしてそうなってしまったんだろう。
引用返信 編集キー/
■65016 / inTopicNo.9)  Re[5]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ もびお (3回)-(2013/01/29(Tue) 19:35:59)
>とっちゃん様、howling様

度々ありがとうございます。
返信が遅くなり申し訳ありません。

提示して頂いたコードを参考にさせて頂き多少処理を改良しました。
ですが、相変わらず動作しない状態です・・・。

> えっと。。。生成しなおすのは、継承クラスですか?
> それとも、基底クラスが保持しているタイマーオブジェクトですか?
生成しなおしているのは継承クラスになります。(また言葉足らずでした・・・)
少しソースを修正したので、各クラスの重要な部分を以下に抜き出してみました。


■上位クラス(←前述の継承クラスをnewしている)
@継承クラス生成
BaseClass Expand = new ExpandA();

A継承クラス再作成(@とは別メソッドで、@よりも後に動作)
Expand = null;
Expand = new ExpandB();

■継承クラス

@コンストラクタ
if (_timer != null)
{
  _timer.Tick -= new EventHandler(timerCallback);
    _timer = null;
}

base._timer =
  new DispatcherTimer(TimeSpan.FromMilliseconds(1000),
    DispatcherPriority.Send, new EventHandler(base.timerCallback),
    Dispatcher.CurrentDispatcher);
base._timer.Stop();

■基底クラス

@メインの処理末尾
_timer.Interval = TimeSpan.FromMilliseconds(1000);
_timer.Start();

Aコールバック関数(Tickイベントで動作)
protected void timerCallback(object sender, EventArgs e)
{
	// 処理いろいろ
}

検証したところ、上位クラスのAを行うとその後のコールバック関数が
呼ばれなくなってしまうようです(Aを省けば動きます)。

> ちなみに、環境は、Win8,VS2012(.NET 4.5 のWPFアプリ。C#で記述)です。
自分はWin7で、後は同じ環境です。

頂いたサンプルソースでは無事動いたので、DispatcherTimer自体には問題なさそうですね・・・。
やはり別のバグという線が濃厚でしょうか?

引用返信 編集キー/
■65020 / inTopicNo.10)  Re[6]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ shu (158回)-(2013/01/30(Wed) 08:41:52)
No65016 (もびお さん) に返信

> ■継承クラス
>
> @コンストラクタ
> if (_timer != null)
> {
>   _timer.Tick -= new EventHandler(timerCallback);
> _timer = null;
> }
>
> base._timer =
>   new DispatcherTimer(TimeSpan.FromMilliseconds(1000),
> DispatcherPriority.Send, new EventHandler(base.timerCallback),
> Dispatcher.CurrentDispatcher);
> base._timer.Stop();
base._timerと_timerは同じもののような気がしますがなぜ記述を変えてあるのでしょう?
おなじくbase.timerCallbakとtimerCallbackも

>
> ■基底クラス
>
> @メインの処理末尾
> _timer.Interval = TimeSpan.FromMilliseconds(1000);
> _timer.Start();
>
> Aコールバック関数(Tickイベントで動作)
> protected void timerCallback(object sender, EventArgs e)
> {
> // 処理いろいろ
> }
>
> 検証したところ、上位クラスのAを行うとその後のコールバック関数が
> 呼ばれなくなってしまうようです(Aを省けば動きます)。
その後のコールバック関数はどのように呼ばれるのでしょうか?記述がない気がします。


> 頂いたサンプルソースでは無事動いたので、DispatcherTimer自体には問題なさそうですね・・・。
> やはり別のバグという線が濃厚でしょうか?
Frameworkに用意されているものを最初に疑うのはどうかと思います。問題解決を先延ばしにする原因になります。
まずは自分の作った部分から疑うようにした方がよいです。


引用返信 編集キー/
■65030 / inTopicNo.11)  Re[7]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ もびお (4回)-(2013/01/30(Wed) 11:09:36)
shu様

返信ありがとうございます。
色々調べたところ、無事解決することができました。

どうやら、UIスレッド以外でDispatcherTimerを使用していたのが原因だったようです。
インスタンス生成、Start、StopはUIスレッドで呼び出さなければいけないようですね・・・。
子スレッドでも動きはしていたので気付くのに遅れてしまいました。

UIスレッドのコンストラクタでインスタンス生成をしてStartおよびStopするだけのメソッドを
用意し、それを子スレッドで呼び出すようにしたところ、無事継承クラス再生成後も動作するようになりました。

UIスレッド以外で使用できないのは仕様なのか、それとも.NETの不具合なのかが気になるところです。

▼参考
http://w.livedoor.jp/wpf/d/DispatcherTimer

> base._timerと_timerは同じもののような気がしますがなぜ記述を変えてあるのでしょう?
> おなじくbase.timerCallbakとtimerCallbackも
記述ミスですね・・・すみません。

色々お騒がせして申し訳ありませんでした。

解決済み
引用返信 編集キー/
■65031 / inTopicNo.12)  Re[8]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ とっちゃん (40回)-(2013/01/30(Wed) 11:33:21)
とっちゃん さんの Web サイト
No65030 (もびお さん) に返信
> どうやら、UIスレッド以外でDispatcherTimerを使用していたのが原因だったようです。
> インスタンス生成、Start、StopはUIスレッドで呼び出さなければいけないようですね・・・。
> 子スレッドでも動きはしていたので気付くのに遅れてしまいました。
>
これを最初に書いていれば、URL 一つ提示して終わったのに。。。

結論を書くと、正しい Dispatcher オブジェクトを渡せていないため、おかしくなっていると思われます。
ちなみに、インスタンスの生成もStart/Stop などのメソッド呼び出しも、どのスレッドからでも呼び出し可能です。

ただし、UIスレッド以外でオブジェクトを生成する場合は、ディスパッチ先のUIスレッドの Dispatcher オブジェクトを渡す必要があります。

このあたりについては、
http://msdn.microsoft.com/ja-jp/library/vstudio/ms741870.aspx
に書かれています。
WPFは、スレッド モデルも従来とは異なる形で取りまとめているので、マルチスレッドアプリを作る場合は注意しておく必要があります。

引用返信 編集キー/
■65037 / inTopicNo.13)  Re[9]: DispatcherTimerのコールバックが動かなくなる
□投稿者/ howling (172回)-(2013/01/30(Wed) 15:41:17)
No65030 (もびお さん) に返信
よくUIスレッド以外からオブジェクトを参照するのはいけない、というのを勉強会なんかでも聞きますし、
今回に限らず、他スレッドからUIスレッドのオブジェクトを直接触るのはやめた方がいいんだろうなぁと。

UIスレッド以外のスレッドがあって〜とかいった話が質問内容に含まれると、
みんながそこなんじゃないか?と目をつける部分ですから良かったんでしょうね。

とはいえ、解決して何よりです。
解決済みもっかい付けておきますね。
ではでは。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -