【发布时间】:2010-12-25 01:51:36
【问题描述】:
我想在我的应用中触发一个事件,该事件在白天的某个时间(例如下午 4:00)连续运行。我想过每秒运行一次计时器,当时间等于下午 4:00 时运行该事件。这样可行。但我想知道是否有办法只在下午 4:00 获得一次回调,而不必继续检查。
【问题讨论】:
我想在我的应用中触发一个事件,该事件在白天的某个时间(例如下午 4:00)连续运行。我想过每秒运行一次计时器,当时间等于下午 4:00 时运行该事件。这样可行。但我想知道是否有办法只在下午 4:00 获得一次回调,而不必继续检查。
【问题讨论】:
这样的事情怎么样,使用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);
请注意,如果您使用 System.Threading.Timer,则由 TimerCallback 指定的回调将在线程池(非 UI)线程上执行 - 因此,如果您计划在 4:00 使用您的 UI 执行某些操作,您必须适当地编组代码(例如,在 Windows 窗体应用程序中使用 Control.Invoke,或在 WPF 应用程序中使用 Dispatcher.Invoke)。
【讨论】:
t.Change(timeUntilFour, Timeout.Infinite); 给出一个错误,指出时间跨度、时间跨度没有过载。我在文档中看到我也可以调用 dispose 但不确定在哪里。有什么想法吗?
t 上的剩余时间。
从 .NET 4.5 开始,有一个非常干净的解决方案:
public async void ScheduleAction(Action action, DateTime ExecutionTime)
{
await Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
action();
}
这是一个没有 async/await 的解决方案:
public void Execute(Action action, DateTime ExecutionTime)
{
Task WaitTask = Task.Delay((int)ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
WaitTask.ContinueWith(_ => action);
WaitTask.Start();
}
应该注意,由于 int32 最大值,这仅适用于大约 24 天,这对于您的情况来说已经足够了,但值得注意。
【讨论】:
async void,action()中的异常会导致整个应用程序崩溃。阅读更多:haacked.com/archive/2014/11/11/async-void-methods
Task.Delay(Date.Subtract(DateTime.Now)); 它会在超过 int32.MaxValue 时抛出 AurgumentOutOfRange。
Task.Delay(-1) 永远不会完成,并且会抛出其他负值。
您可以使用Task Sceduler on windows 了解详情,请参阅daily trigger example。
如果你想自己写,也可以使用下面的代码:
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
}
【讨论】:
以 VoteCoffees 为主导,这是一个基于事件的紧凑型解决方案:
/// <summary>
/// Utility class for triggering an event every 24 hours at a specified time of day
/// </summary>
public class DailyTrigger : IDisposable
{
/// <summary>
/// Time of day (from 00:00:00) to trigger
/// </summary>
TimeSpan TriggerHour { get; }
/// <summary>
/// Task cancellation token source to cancel delayed task on disposal
/// </summary>
CancellationTokenSource CancellationToken { get; set; }
/// <summary>
/// Reference to the running task
/// </summary>
Task RunningTask { get; set; }
/// <summary>
/// Initiator
/// </summary>
/// <param name="hour">The hour of the day to trigger</param>
/// <param name="minute">The minute to trigger</param>
/// <param name="second">The second to trigger</param>
public DailyTrigger(int hour, int minute = 0, int second = 0)
{
TriggerHour = new TimeSpan(hour, minute, second);
CancellationToken = new CancellationTokenSource();
RunningTask = Task.Run(async () =>
{
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, CancellationToken.Token);
OnTimeTriggered?.Invoke();
}
}, CancellationToken.Token);
}
/// <inheritdoc/>
public void Dispose()
{
CancellationToken?.Cancel();
CancellationToken?.Dispose();
CancellationToken = null;
RunningTask?.Dispose();
RunningTask = null;
}
/// <summary>
/// Triggers once every 24 hours on the specified time
/// </summary>
public event Action OnTimeTriggered;
/// <summary>
/// Finalized to ensure Dispose is called when out of scope
/// </summary>
~DailyTrigger() => Dispose();
}
消费者:`
void Main()
{
var trigger = new DailyTrigger(16); // every day at 4:00pm
trigger.OnTimeTriggered += () =>
{
// Whatever
};
Console.ReadKey();
}
【讨论】:
.NET 有很多计时器类,但它们都占用相对于当前时间的时间跨度。对于相对时间,在启动计时器之前需要考虑很多事情,并在计时器运行时进行监控。
操作系统非常适合处理这种复杂性。 .NET 上运行的应用程序代码不是。
对于 Windows,NuGet 包 AbsoluteTimer 包装了一个在绝对时间到期的操作系统计时器。
【讨论】:
我这样做是为了每天早上 7 点开火
bool _ran = false; //initial setting at start up
private void timer_Tick(object sender, EventArgs e)
{
if (DateTime.Now.Hour == 7 && _ran==false)
{
_ran = true;
Do_Something();
}
if(DateTime.Now.Hour != 7 && _ran == true)
{
_ran = false;
}
}
【讨论】:
任务调度器是更好的选择,在C#中可以轻松使用,http://taskscheduler.codeplex.com/
【讨论】:
与 Dan 的解决方案相呼应,使用 Timercallback 是一种快速而简洁的解决方案。 在要安排要运行的任务或子例程的方法中,使用以下命令:
t = New Timer(Sub()
'method call or code here'
End Sub, Nothing, 400, Timeout.Infinite)
使用“Timeout.Infinite”将确保回调仅在 400 毫秒后执行一次。我正在使用 VB.Net。
【讨论】:
这个解决方案怎么样?
Sub Main()
Dim t As New Thread(AddressOf myTask)
t.Start()
Console.ReadLine()
End Sub
Private Sub myTask()
Dim a = "14:35"
Dim format = "dd/MM/yyyy HH:mm:ss"
Dim targetTime = DateTime.Parse(a)
Dim currentTime = DateTime.Parse(Now.ToString(format))
Console.WriteLine(currentTime)
Console.WriteLine("target time " & targetTime)
Dim bb As TimeSpan = targetTime - currentTime
If bb.TotalMilliseconds < 0 Then
targetTime = targetTime.AddDays(1)
bb = targetTime - currentTime
End If
Console.WriteLine("Going to sleep at " & Now.ToString & " for " & bb.TotalMilliseconds)
Thread.Sleep(bb.TotalMilliseconds)
Console.WriteLine("Woke up at " & Now.ToString(format))
End Sub
【讨论】: