让一个 .net System.Timers.Timer 立即触发

25

我正在使用一个计时器来周期性地运行一个事件(间隔为2分钟),这个已经正常工作。但是我希望在计时器创建时立即触发事件,而不是等待2分钟。

请注意,我不能通过调用方法来实现这一点,因为它需要一些时间才能运行,并且会阻塞应用程序。我需要计时器像平常一样触发并在单独的线程中运行事件。

目前我能想到的最好的方法是子类化计时器并创建一个TriggerManually方法,该方法会执行以下操作:

  • 关闭自动重置
  • 将间隔设置为1毫秒
  • 启用计时器

这将立即触发过去的事件,然后我可以将所有设置恢复为正常状态。

虽然这似乎有点绕,但有更好的方法吗?

3个回答

21

你不能直接手动调用计时器的已过时间事件吗?

即使你期望它在线程池线程上执行,你仍然可以调用它。

class Blah
{
    private Timer mTimer;

    public Blah()
    {
        mTimer = new Timer(120000);

        ElapsedEventHandler handler = new ElapsedEventHandler(Timer_Elapsed);
        mTimer.Elapsed += handler;
        mTimer.Enabled = true;

        //Manually execute the event handler on a threadpool thread.
        handler.BeginInvoke(this, null, new AsyncCallback(Timer_ElapsedCallback), handler);
    }

    private static void Timer_Elapsed(object source, ElapsedEventArgs e)
    {
        //Do stuff...
    }

    private void Timer_ElapsedCallback(IAsyncResult result)
    {
        ElapsedEventHandler handler = result.AsyncState as ElapsedEventHandler;
        if (handler != null)
        {
            handler.EndInvoke(result);
        }
    }
}

8
我喜欢Rob Cooke的回答,因此我建了一个小型的EagerTimer类,它是System.Timers.Timer的子类,并添加了此功能。(这些提示来自这些文章
我知道我也可以使用System.Threading.Timer,但这个类对于我的应用程序来说更简单并且运作良好。 EagerTimer
/// <summary>
// EagerTimer is a simple wrapper around System.Timers.Timer that
// provides "set up and immediately execute" functionality by adding a
// new AutoStart property, and also provides the ability to manually
// raise the Elapsed event with RaiseElapsed.
/// </summary>
public class EagerTimer : Timer
{
    public EagerTimer()
        : base() { }

    public EagerTimer(double interval)
        : base(interval) { }

    // Need to hide this so we can use Elapsed.Invoke below
    // (otherwise the compiler complains)
    private event ElapsedEventHandler _elapsedHandler;
    public new event ElapsedEventHandler Elapsed
    {
        add { _elapsedHandler += value; base.Elapsed += value; }
        remove { _elapsedHandler -= value; base.Elapsed -= value; }
    }

    public new void Start()
    {
        // If AutoStart is enabled, we need to invoke the timer event manually
        if (AutoStart)
        {
            this._elapsedHandler.BeginInvoke(this, null, new AsyncCallback(AutoStartCallback), _elapsedHandler); // fire immediately
        }

        // Proceed as normal
        base.Start();
    }

    private void AutoStartCallback(IAsyncResult result)
    {
        ElapsedEventHandler handler = result.AsyncState as ElapsedEventHandler;
        if (handler != null) handler.EndInvoke(result);
    }

    // Summary:
    //     Gets or sets a value indicating whether the EagerTimer should raise
    //     the System.Timers.Timer.Elapsed event immediately when Start() is called,
    //     or only after the first time it elapses. If AutoStart is false, EagerTimer behaves
    //     identically to System.Timers.Timer.
    //
    // Returns:
    //     true if the EagerTimer should raise the System.Timers.Timer.Elapsed
    //     event immediately when Start() is called; false if it should raise the System.Timers.Timer.Elapsed
    //     event only after the first time the interval elapses. The default is true.
    [Category("Behavior")]
    [DefaultValue(true)]
    [TimersDescription("TimerAutoStart")]
    public bool AutoStart { get; set; }

    /// <summary>
    /// Manually raises the Elapsed event of the System.Timers.Timer.
    /// </summary>
    public void RaiseElapsed()
    {
        if (_elapsedHandler != null)
            _elapsedHandler(this, null);
    }
}

单元测试

[TestClass]
public class Objects_EagerTimer_Tests
{
    private const int TimerInterval = 10; // ms

    private List<DateTime> _timerFires = new List<DateTime>();
    private DateTime _testStart;

    [TestInitialize]
    public void TestSetup()
    {
        _timerFires.Clear();
        _testStart = DateTime.Now;
    }

    [TestMethod]
    public void Objects_EagerTimer_WithAutoStartDisabled()
    {
        // EagerTimer should behave as a normal System.Timers.Timer object
        var timer = new EagerTimer(TimerInterval);
        timer.AutoReset = false;
        timer.Elapsed += timerElapsed;
        timer.Start();

        // Wait (not enough time for first interval)
        Thread.Sleep(5);
        Assert.IsFalse(_timerFires.Any());

        // Wait a little longer
        Thread.Sleep(TimerInterval);
        Assert.AreEqual(1, _timerFires.Count);
    }

    [TestMethod]
    public void Objects_EagerTimer_WithAutoStartEnabled()
    {
        // EagerTimer should fire immediately on Start()
        var timer = new EagerTimer(TimerInterval);
        timer.AutoReset = false;
        timer.AutoStart = true;
        timer.Elapsed += timerElapsed;
        timer.Start();

        // Wait (not enough time for first interval)
        Thread.Sleep(5);
        Assert.IsTrue(_timerFires.Any());

        // Wait a little longer, now it will have fired twice
        Thread.Sleep(TimerInterval);
        Assert.AreEqual(2, _timerFires.Count);
    }

    [TestMethod]
    public void Objects_EagerTimer_WhenRaisingManually()
    {
        // EagerTimer should fire immediately on Start()
        var timer = new EagerTimer(TimerInterval);
        timer.AutoReset = false;
        timer.AutoStart = false;
        timer.Elapsed += timerElapsed;

        Assert.IsFalse(_timerFires.Any());
        timer.RaiseElapsed();
        Assert.IsTrue(_timerFires.Any());
    }

    private void timerElapsed(object sender, ElapsedEventArgs e) {
        _timerFires.Add(DateTime.Now);
    }
}

如果您能将此转换为我可以使用Nuget下载的内容,那将是很好的。 - Jez
顺便说一下,我在你的类中添加了一个方便的方法,作为“Start”调用的一部分自动启动;我建议你在你的答案中添加它:public void Start(bool withAutoStart) { if (withAutoStart) { AutoStart = true; } Start(); } - Jez

5

@Mitch - 这只证明了你是个有理智的人,尽管你的声望很高 :) - Gishu
没错,这个可行。System.Threading.Timer 使用起来有点困难,但它确实满足了我的需求。谢谢! - Nathan
2
请注意,System.Threading.Timer回调中未处理的异常会导致您的应用程序崩溃。 - ivan_pozdeev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接