Приложение для будильника в .Net
Я не пишу приложение будильника, но это поможет проиллюстрировать мой вопрос.
Скажем, что у меня есть метод в моем приложении, и я хочу, чтобы этот метод вызывался каждый час в час (например, в 19:00, 20:00, 21:00 и т.д.). Я мог бы создать таймер и установить его интервал до 3600000, но в конечном итоге это приведет к сбою синхронизации с системными часами. Или я мог бы использовать цикл while()
с Thread.Sleep(n)
для периодической проверки системного времени и вызова метода, когда будет достигнуто желаемое время, но мне это тоже не нравится (Thread.Sleep(n)
- большой запах кода для меня).
То, что я ищу, - это некоторый метод в .Net, который позволяет мне передать в будущем объект DateTime и делегат метода или обработчик событий, но я не смог найти такую вещь. Я подозреваю, что в этом Win32 API есть метод, но я тоже этого не смог найти.
Ответы
Ответ 1
Или вы можете создать таймер с интервалом в 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!");
Обратите внимание, что приведенный выше код очень отрывочный, а не потокобезопасный.
Ответ 2
Интересно, что на самом деле я столкнулся с очень похожими проблемами и пошел искать метод в инфраструктуре .NET, который бы справился с этим сценарием. В конце концов, мы завершили внедрение нашего собственного решения, которое было вариацией цикла while/Thread.Sleep(n), где n становится меньше, чем ближе вы достигаете желаемого целевого времени (логарифмически на самом деле, но с некоторыми разумными порогами, поэтому вы не максимизируете процессор, когда приближаетесь к целевому времени.) Здесь очень простая реализация, которая просто спит в течение половины времени между настоящим и целевым временем.
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();
}
}
Ответ 3
Вы можете просто reset длительность таймера каждый раз, когда он срабатывает, например:
// 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;
}
Ответ 4
Вы можете создать класс 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);
}
}
}
Ответ 5
Как насчет класса System.Timers.Timer? См. http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx
Ответ 6
Я использовал это с большим успехом:
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
С#:
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();
}
}
Я не знаю, работает ли С#, но vb работает отлично.
Использование в VB:
Dim clock As New AlarmClock
clock.interval = 1 'Interval is in hours, could easily convert to anything else
clock.StartClock()
Затем просто добавьте обработчик событий для события SoundAlarm.