Приложение для будильника в .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);
        }
    }
}

Ответ 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.