web-dev-qa-db-fra.com

Comment utiliser la classe .NET Timer pour déclencher un événement à un moment précis?

Je souhaite qu'un événement soit déclenché dans mon application et qu'il s'exécute en continu pendant la journée à une certaine heure, disons à 16 h 00. J'ai pensé à faire fonctionner le chronomètre toutes les secondes et lorsque l'heure est égale à 16 h, lancez l'événement. Ça marche. Mais je me demande s'il existe un moyen de récupérer le rappel une fois à 16h00 et de ne pas avoir à continuer de vérifier.

42
Behrooz Karjoo

Que diriez-vous de quelque chose comme ça, en utilisant le System.Threading.Timer classe?

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);

Notez que si vous utilisez un System.Threading.Timer, le rappel spécifié par TimerCallback sera exécuté sur un thread de pool de threads (non-UI) - donc si vous prévoyez de faire quelque chose avec votre UI à 4h00, vous devrez marshaler le code de manière appropriée (par exemple, en utilisant Control.Invoke dans une application Windows Forms ou Dispatcher.Invoke dans une application WPF).

43
Dan Tao

À partir de .NET 4.5, il existe une solution vraiment propre:

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

Voici une solution sans async/wait:

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

Il convient de noter que cela ne fonctionne que pendant environ 24 jours en raison de la valeur maximale int32, ce qui est suffisant pour votre cas, mais il convient de le noter.

27
VoteCoffee

Vous pouvez utiliser Planificateur de tâches sur Windows Voir exemple de déclencheur quotidien pour plus de détails.

ou utilisez le code ci-dessous si vous voulez l'écrire vous-même:

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
}
6
Saeed Amiri

Prenant l'exemple de VoteCoffees, voici une solution compacte basée sur les événements:

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;
}

Consommateur: `

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

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

    Console.ReadKey();
}
4
noontz

.NET a beaucoup de classes de temporisation, mais elles prennent toutes des intervalles de temps par rapport à l'heure actuelle. Avec un temps relatif, il y a beaucoup de choses à considérer avant de démarrer votre minuterie et à surveiller pendant que votre minuterie fonctionne.

  • Que faire si l'ordinateur passe en veille?
  • Et si l'heure de l'ordinateur change?
  • Que se passe-t-il si l'heure d'expiration se situe après une transition d'heure d'été?
  • Que se passe-t-il si l'utilisateur modifie le fuseau horaire de l'ordinateur après avoir démarré votre minuterie de sorte qu'il y ait une nouvelle transition de fuseau horaire avant l'expiration qui n'existait pas auparavant?

Le système d'exploitation est bien placé pour gérer cette complexité. Le code d'application s'exécutant sur .NET ne l'est pas.

Pour Windows, le package NuGet AbsoluteTimer encapsule un minuteur de système d'exploitation qui expire à une heure absolue.

0
Edward Brey

Le planificateur de tâches est une meilleure option, et il peut être facilement utilisé en C #, http://taskscheduler.codeplex.com/

0
Lex Li

Echo to Dan's solution, using Timercallback est une solution rapide et soignée. Dans la méthode que vous souhaitez planifier l'exécution d'une tâche ou d'un sous-programme, utilisez ce qui suit:

    t = New Timer(Sub()
                        'method call or code here'
                  End Sub, Nothing, 400, Timeout.Infinite)

l'utilisation de 'Timeout.Infinite' garantira que le rappel ne sera exécuté qu'une seule fois après 400 ms. J'utilise VB.Net.

0
Joseph Wu

Et cette solution?

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
0
Tal Aruety

J'ai fait cette façon de tirer 7 heures du matin chaque matin

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;
        }

    }
0
Jon649