web-dev-qa-db-ja.com

タイマーを使って待つ方法は?

タイマーを使用してメソッドのイベントを遅延させようとしていますが、タイマーを使用して待機する方法が必ずしもわかりません。

タイマーを2秒に設定しましたが、このコードを実行すると、最後の呼び出しは2秒の遅延なしで実行されます。

Timer timer = new Timer();
timer.Tick += new EventHandler(timer_Tick); // Everytime timer ticks, timer_Tick will be called
timer.Interval = (1000) * (2);              // Timer will tick evert second
timer.Enabled = true;                       // Enable the timer


void timer_Tick(object sender, EventArgs e)
{
    timer.Stop();
}

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    timer.Start();
    label1.Text = "second";
}

したがって、ボタンをクリックすると、label1がすぐに「秒」として表示されます。「最初」に変更して2秒待ってから「秒」に変更するのではありません。 thread.sleepの代わりにタイマーを使用することについて、ここで多くのスレッドを読みましたが、実際にそれを実装する方法を見つけたり、図にしたりすることができません。

12
Fuzz Evans

timer.Start()はタイマーを開始するだけですが、タイマーがバックグラウンドで実行されている間はすぐに戻ります。したがって、ラベルテキストをfirstsecondに設定する間に一時停止はほとんどありません。あなたがしたいことは、タイマーが作動するのを待ってから、もう一度ラベルを更新することです:

_void timer_Tick(object sender, EventArgs e)
{
    timer.Stop();
    label1.Text = "second";
}

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    timer.Start();
}
_

ところで_timer.Enabled_をtrueに設定しないでください。すでにtimer.Start()を使用してタイマーを開始しています。

コメントで述べたように、タイマーの作成を次のようにメソッドに入れることができます(注:これはテストされていません)。

_public void Delayed(int delay, Action action)
{
    Timer timer = new Timer();
    timer.Interval = delay;
    timer.Tick += (s, e) => {
        action();
        timer.Stop();
    };
    timer.Start();
}
_

そして、あなたはこのようにそれを使うことができます:

_private void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    Delayed(2000, () => label1.Text = "second");
}
_

Tergiverのフォローアップ

Delayedを使用すると、メモリリーク(参照リーク)が発生しますか?

イベントをサブスクライブすると、常に双方向の参照が作成されます。

この場合、_timer.Tick_は無名関数(lambda)への参照を取得します。この関数はローカル変数timerを持ち上げますが、これは値ではなく参照であり、渡されたアクションデリゲートへの参照を含みます。そのデリゲートには、Formのインスタンスメンバーである_label1_への参照が含まれます。では、TimerからFormへの循環参照はありますか?

答えはわかりませんが、理由を説明するのは少し難しいと思います。わからないので、Delayedでのラムダの使用を削除し、適切なメソッドにして、タイマー(メソッドのsenderパラメーター)を停止することに加えて、イベントも削除します。

通常、ラムダはガベージコレクションに問題を引き起こしません。この場合、タイマーインスタンスはローカルにのみ存在し、ラムダ内の参照は、ガベージコレクションによるインスタンスの収集を妨げません( この質問 も参照)。

.NET Memory Profilerを使用して、これを実際にテストしました。タイマーオブジェクトは問題なく収集され、リークは発生しませんでした。プロファイラーから、「[...]が適切に廃棄されずにガベージコレクションされている」場合があるという警告が表示されました。 (それへの参照を維持することによって)イベントハンドラ自体を削除しても、それは修正されませんでした。キャプチャしたタイマー参照を_(Timer)s_に変更しても、変更されませんでした。

明らかに何が役に立ったかは、タイマーを停止した後、イベントハンドラーでtimer.Dispose()を呼び出すことでしたが、それが実際に必要かどうかを私は主張します。プロファイラーの警告/メモはそれほど重要ではないと思います。

11
poke

C#5.0を使用している場合、awaitを使用するとmuchが簡単になります。

private async void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    await Task.Delay(2000);
    label1.Text = "second";
}
8
Servy
       private bool Delay(int millisecond)       
       {

           Stopwatch sw = new Stopwatch();
           sw.Start();
           bool flag = false;
           while (!flag)
           {
               if (sw.ElapsedMilliseconds > millisecond) 
               {
                  flag = true;
               }
           }
           sw.Stop();
           return true;

       }

        bool del = Delay(1000);
0
Yuval

タイマーがカチカチ音を立てたときにテキストを変更するだけの場合は、プットオフするほうがいいでしょう...

label1.Text = "second";

...タイマーティックで、タイマーをenabled = falseに変更する前または後。

そのようです;

void timer_Tick(object sender, EventArgs e)
{
  timer.Stop();
  label1.Text = "second";
}

private void button1_Click(object sender, EventArgs e)
{
  label1.Text = "first";
  timer.Start();
}
0
Shane.C