我不是在写闹钟应用程序,但它有助于说明我的问题。
假设我的应用程序中有一个方法,我希望每小时每小时调用一次该方法(例如,晚上7点,晚上8点,晚上9点等)。我可以创建一个Timer并将其Interval设置为3600000,但最终这会与系统时钟失去同步。或者我可以使用while()
循环与Thread.Sleep(n)
定期检查系统时间并在达到所需时间时调用方法,但我也不喜欢( Thread.Sleep(n)
对我来说是一个很大的代码味道。)
我正在寻找的是.Net中的一些方法,它允许我传入未来的DateTime对象和方法委托或事件处理程序,但我无法找到任何这样的东西。我怀疑Win32 API中有一个方法可以做到这一点,但我也无法找到它。
答案 0 :(得分:11)
或者,您可以创建一个间隔为1秒的计时器,并每秒检查当前时间,直到达到事件时间为止,如果是,则提升您的事件。
你可以为它做一个简单的包装:
public class AlarmClock
{
public AlarmClock(DateTime alarmTime)
{
this.alarmTime = alarmTime;
timer = new Timer();
timer.Elapsed += timer_Elapsed;
timer.Interval = 1000;
timer.Start();
enabled = true;
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(enabled && DateTime.Now > alarmTime)
{
enabled = false;
OnAlarm();
timer.Stop();
}
}
protected virtual void OnAlarm()
{
if(alarmEvent != null)
alarmEvent(this, EventArgs.Empty);
}
public event EventHandler Alarm
{
add { alarmEvent += value; }
remove { alarmEvent -= value; }
}
private EventHandler alarmEvent;
private Timer timer;
private DateTime alarmTime;
private bool enabled;
}
用法:
AlarmClock clock = new AlarmClock(someFutureTime);
clock.Alarm += (sender, e) => MessageBox.Show("Wake up!");
请注意上面的代码非常粗略,而且不是线程安全的。
答案 1 :(得分:8)
有趣的是,我实际上遇到了一个非常类似的问题,并在.Net框架中寻找可以处理这种情况的方法。最后,我们最终实现了我们自己的解决方案,这是一个带有Thread.Sleep(n)的while循环的变体,其中n越小,越接近所需的目标时间(实际上是对数,但是有一些合理的阈值,所以当你接近目标时间时,你不是最大化cpu。)这是一个非常简单的实现,它只能在现在和目标时间之间休眠一半。
class Program
{
static void Main(string[] args)
{
SleepToTarget Temp = new SleepToTarget(DateTime.Now.AddSeconds(30),Done);
Temp.Start();
Console.ReadLine();
}
static void Done()
{
Console.WriteLine("Done");
}
}
class SleepToTarget
{
private DateTime TargetTime;
private Action MyAction;
private const int MinSleepMilliseconds = 250;
public SleepToTarget(DateTime TargetTime,Action MyAction)
{
this.TargetTime = TargetTime;
this.MyAction = MyAction;
}
public void Start()
{
new Thread(new ThreadStart(ProcessTimer)).Start();
}
private void ProcessTimer()
{
DateTime Now = DateTime.Now;
while (Now < TargetTime)
{
int SleepMilliseconds = (int) Math.Round((TargetTime - Now).TotalMilliseconds / 2);
Console.WriteLine(SleepMilliseconds);
Thread.Sleep(SleepMilliseconds > MinSleepMilliseconds ? SleepMilliseconds : MinSleepMilliseconds);
Now = DateTime.Now;
}
MyAction();
}
}
答案 2 :(得分:2)
您可以在每次触发时重置计时器持续时间,如下所示:
// using System.Timers;
private void myMethod()
{
var timer = new Timer {
AutoReset = false, Interval = getMillisecondsToNextAlarm() };
timer.Elapsed += (src, args) =>
{
// Do timer handling here.
timer.Interval = getMillisecondsToNextAlarm();
timer.Start();
};
timer.Start();
}
private double getMillisecondsToNextAlarm()
{
// This is an example of making the alarm go off at every "o'clock"
var now = DateTime.Now;
var inOneHour = now.AddHours(1.0);
var roundedNextHour = new DateTime(
inOneHour.Year, inOneHour.Month, inOneHour.Day, inOneHour.Hour, 0, 0);
return (roundedNextHour - now).TotalMilliseconds;
}
答案 3 :(得分:1)
您可以创建一个具有专用线程的Alarm类,该线程将在指定时间内进入休眠状态,但这将使用Thread.Sleep方法。类似的东西:
/// <summary>
/// Alarm Class
/// </summary>
public class Alarm
{
private TimeSpan wakeupTime;
public Alarm(TimeSpan WakeUpTime)
{
this.wakeupTime = WakeUpTime;
System.Threading.Thread t = new System.Threading.Thread(TimerThread) { IsBackground = true, Name = "Alarm" };
t.Start();
}
/// <summary>
/// Alarm Event
/// </summary>
public event EventHandler AlarmEvent = delegate { };
private void TimerThread()
{
DateTime nextWakeUp = DateTime.Today + wakeupTime;
if (nextWakeUp < DateTime.Now) nextWakeUp = nextWakeUp.AddDays(1.0);
while (true)
{
TimeSpan ts = nextWakeUp.Subtract(DateTime.Now);
System.Threading.Thread.Sleep((int)ts.TotalMilliseconds);
try { AlarmEvent(this, EventArgs.Empty); }
catch { }
nextWakeUp = nextWakeUp.AddDays(1.0);
}
}
}
答案 4 :(得分:0)
我知道这是一个老问题,但是当我在寻找其他问题的答案时遇到了这个问题。我以为我会在这里扔两分钱,因为我最近有这个特别的问题。
您可以做的另一件事是安排如下方法:
/// Schedule the given action for the given time.
public async void ScheduleAction ( Action action , DateTime ExecutionTime )
{
try
{
await Task.Delay ( ( int ) ExecutionTime.Subtract ( DateTime.Now ).TotalMilliseconds );
action ( );
}
catch ( Exception )
{
// Something went wrong
}
}
请记住,它最多只能等待int 32的最大值(大约一个月左右),它应该可以满足您的目的。用法:
void MethodToRun ( )
{
Console.WriteLine ("Hello, World!");
}
void CallingMethod ( )
{
var NextRunTime = DateTime.Now.AddHours(1);
ScheduleAction ( MethodToRun, NextRunTime );
}
您应该在一个小时内收到控制台消息。
答案 5 :(得分:-1)
System.Timers.Timer类怎么样?见http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx
答案 6 :(得分:-2)
我之前使用过这个并取得了巨大的成功:
Vb.net:
Imports System.Threading
Public Class AlarmClock
Public startTime As Integer = TimeOfDay.Hour
Public interval As Integer = 1
Public Event SoundAlarm()
Public Sub CheckTime()
While TimeOfDay.Hour < startTime + interval
Application.DoEvents()
End While
RaiseEvent SoundAlarm()
End Sub
Public Sub StartClock()
Dim clockthread As Thread = New Thread(AddressOf CheckTime)
clockthread.Start()
End Sub
End Class
C#:
using System.Threading;
public class AlarmClock
{
public int startTime = TimeOfDay.Hour;
public int interval = 1;
public event SoundAlarmEventHandler SoundAlarm;
public delegate void SoundAlarmEventHandler();
public void CheckTime()
{
while (TimeOfDay.Hour < startTime + interval) {
Application.DoEvents();
}
if (SoundAlarm != null) {
SoundAlarm();
}
}
public void StartClock()
{
Thread clockthread = new Thread(CheckTime);
clockthread.Start();
}
}
我不知道c#是否有效,但vb工作正常。
VB中的用法:
Dim clock As New AlarmClock
clock.interval = 1 'Interval is in hours, could easily convert to anything else
clock.StartClock()
然后,只需为SoundAlarm事件添加一个事件处理程序。