如何使用.NET Timer类在特定时间触发事件?

Beh*_*joo 42 .net c# timer

我想在我的应用程序中触发一个事件,该事件在某一时间持续运行,比如说在下午4点.我想过每秒运行一次计时器,当时间等于下午4点时,运行该事件.这样可行.但我想知道是否有一种方法可以在下午4:00获得回调,而不必继续检查.

Dan*_*Tao 44

这个怎么样,用这个System.Threading.Timer课程?

var t = new Timer(TimerCallback);

// Figure how much time until 4:00
DateTime now = DateTime.Now;
DateTime fourOClock = DateTime.Today.AddHours(16.0);

// If it's already past 4:00, wait until 4:00 tomorrow    
if (now > fourOClock)
{
    fourOClock = fourOClock.AddDays(1.0);
}

int msUntilFour = (int)((fourOClock - now).TotalMilliseconds);

// Set the timer to elapse only once, at 4:00.
t.Change(msUntilFour, Timeout.Infinite);
Run Code Online (Sandbox Code Playgroud)

请注意,如果使用a System.Threading.Timer,则指定的回调TimerCallback将在线程池(非UI)线程上执行 - 因此,如果您计划在4:00对UI执行某些操作,则必须编组代码适当的(例如,Control.Invoke在Windows窗体应用程序中或Dispatcher.Invoke在WPF应用程序中使用).

  • 此代码假定从午夜到下午 4:00 为 16 小时。该假设在夏令时转换期间无效。 (4认同)
  • 该代码还假定系统时钟始终准确。如果有错误并且需要调整,代码不会检测到它并相应地调整它在 `t` 上的剩余时间。 (2认同)

Vot*_*fee 28

从.NET 4.5开始,有一个非常干净的解决方案:

public async void ScheduleAction(Action action, DateTime ExecutionTime)
{
    await Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
    action();
}
Run Code Online (Sandbox Code Playgroud)

这是一个没有async/await的解决方案:

public void Execute(Action action, DateTime ExecutionTime)
{
    Task WaitTask = Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
    WaitTask.ContinueWith(_ => action);
    WaitTask.Start();
}
Run Code Online (Sandbox Code Playgroud)

应该注意的是,由于int32最大值,这仅适用于大约24天,这对于您的情况很有用,但值得注意.

  • 注意不要使用 `async void`,`action()` 中的异常会导致整个应用程序崩溃。阅读更多:http://haacked.com/archive/2014/11/11/async-void-methods/ (3认同)
  • 实际上,您现在可以将TimeSpan用于Task.Delay,这意味着您可以超过VoteCoffee提到的24天限制.TimeSpan基本没有限制,比如1000万天,https://msdn.microsoft.com/en-us/library/system.timespan.maxvalue(v=vs.110).aspx (2认同)
  • 很好的解决方案,但"ExecutionTime"必须在将来或Task.Delay将抛出ArgumentOutOfRangeException. (2认同)
  • @ chris84948,即使给它一个时间跨度,例如Task.Delay(Date.Subtract(DateTime.Now));`超过int32.MaxValue时,它也会抛出AurgumentOutOfRange。 (2认同)

Sae*_*iri 6

您可以在Windows上使用Task Sceduler查看每日触发示例以获取详细信息.

如果您想自己编写,请使用以下代码:

public void InitTimer()
{
    DateTime time = DateTime.Now;
    int second = time.Second;
    int minute = time.Minute;
    if (second != 0)
    {
        minute = minute > 0 ? minute-- : 59;
    }

    if (minute == 0 && second == 0)
    {
        // DoAction: in this function also set your timer interval to 24 hours
    }
    else
    {
        TimeSpan span = //new daily timespan, previous code was hourly: new TimeSpan(0, 60 - minute, 60 - second);
        timer.Interval = (int) span.TotalMilliseconds - 100; 
        timer.Tick += new EventHandler(timer_Tick);
        timer.Start();
    }
}

void timer_Tick(object sender, EventArgs e)
{
    timer.Interval = ...; // 24 hours
    // DoAction
}
Run Code Online (Sandbox Code Playgroud)


noo*_*ntz 5

以 VoteCoffees 为主导,这是一个基于事件的紧凑解决方案:

public class DailyTrigger 
{
    readonly TimeSpan triggerHour;  

    public DailyTrigger(int hour, int minute = 0, int second = 0)
    {
        triggerHour = new TimeSpan(hour, minute, second);
        InitiateAsync();        
    }

    async void InitiateAsync()
    {
        while (true)
        {
            var triggerTime = DateTime.Today + triggerHour - DateTime.Now;
            if (triggerTime < TimeSpan.Zero)
                triggerTime = triggerTime.Add(new TimeSpan(24,0,0));
            await Task.Delay(triggerTime);
            OnTimeTriggered?.Invoke();
        }
    }

    public event Action OnTimeTriggered;
}
Run Code Online (Sandbox Code Playgroud)

消费者:`

void Main()
{
    var trigger = new DailyTrigger(16); // every day at 4:00pm

    trigger.OnTimeTriggered += () => 
    {
        // Whatever
    };  

    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

  • 很高兴这个解决方案提供了 CancellationToken - 对我的上下文很有用。 (2认同)