Cameron Raymond
Cameron Raymond

Reputation: 33

Set up code to run every 24 hours

For a side project at work I'm trying to have a piece of code that is run every 24 hours at a certain time. My boss asked me to use an infinite loop instead of C#'s timer class so that's the main constraint I'm working with. My problem is that the code will work for the first 24 hours (i.e. it will run the code on the day that I set it to) but then it won't update after that. I'm not being thrown any exceptions or errors so I'm assuming it's just a problem with my logic.

Here's the gist of the code I have now.

int i = 0;
while (true)
{
    DateTime currentdate = DateTime.Now;
    String time = currentdate.ToString("HH:mm");

    if ((time == "23:50" || time == "23:51") && i == 0)
    {
        HistoricalAverageCollection HAC = new HistoricalAverageCollection();
        HAC.ExecHAC();

        HistoricalAverageError HAE = new HistoricalAverageError();
        HAE.ExecHAE();
        FourWeekAverageCollection FWAC = new FourWeekAverageCollection();
        FWAC.ExecFWAC();
        FourWeekAverageError FWAE = new FourWeekAverageError();
        FWAE.ExecFWAE();

        DomainsReturningZeroTwentyFourHours DRZ = 
            new DomainsReturningZeroTwentyFourHours();
        DRZ.ExecDomainsReturningZero();

        context.SaveChanges();

        //Program should update every 24 horus
        i = 1;

        Console.Write("Updated The Historical Averages In The Data Base at...");
        Console.Write(DateTime.Now);
        Console.WriteLine("i is -> {0}", i);
        Console.Read();
    }
    else if (time == "06:00" && i == 1)
    {
        ReportEmail Report = new ReportEmail();
        Report.CreateAndSendReport();
        i = 0;
        Console.Write("counter reset. I is now -> {0} /n Email Sent",i);
        Console.Read();
    }
}

The code is set up to call a bunch of tsql stored procedures at 11:50 Pm and then send out an email report based on that data at 6 in the morning. However, it will only run once and I find myself waking up in the morning a day or two later and seeing that no emails are being sent.

Any help would be appreciated :)

Upvotes: 0

Views: 2357

Answers (3)

antiduh
antiduh

Reputation: 12425

If you really want to do this all by hand without using timers or existing scheduler facilities, I'd recommend being a little more rigorous and building a simple task scheduler class yourself. The parts you'll need:

  • A class to store each task, which includes the code to execute for the task and the schedule for when each task should run.
  • A list of such tasks
  • A method to compute when the next deadline is from your list of tasks, so you know how long to sleep for.
  • A SemaphoreSlim to sleep on (instead of Thread.Sleep())
    • Use a SemaphoreSlim because it acts as a Thread.Sleep() by passing it a wait time if the semaphore is never released; but also because you can manually release it if your scheduler determines that a new task has been added and it should wake up to re-evaluate the next deadline.

I'd recommend storing your deadlines in UTC, and doing the majority of the time computation work using UTC time, this way there's no confusion about time zone changes or DST.

You also should consider not just sleeping for the entire time until the next deadline, just in case there are NTP updates to the PC's system time. Consider sleeping for a maximum of 1 hour at a time.

Some highlights to get you started:

public void Run()
{
    this.running = true;

    do
    {
        DateTime nextDeadlineUtc;
        ScheduledTask nextTask;
        bool deadlineExpired;

        nextDeadlineUtc = ComputeNextDeadline( out nextTask );

        deadlineExpired = WaitForDeadline( nextDeadlineUtc );

        if( deadlineExpired )
        {
            // We hit the deadline. Execute the task and move on.
            nextTask.Execute();
        }
        else
        {
            // We were woken up before the deadline expired. That means either we're shutting
            // down, or we need to recompute our next deadline because the schedule changed.
            // To deal with this, just do nothing. We'll loop back around and either find out
            // we're being asked to stop, or we'll recompute the next deadline.
        }
    }
    while( this.running );
}

/// <summary>
/// Sleeps until the deadline has expired.
/// </summary>
/// <param name="nextDeadlineUtc">The next deadline, in UTC</param>
/// <returns>
/// True if the deadline has elapsed; false if the scheduler should re-examine its next deadline.
/// </returns>
private bool WaitForDeadline( DateTime nextDeadlineUtc )
{
    TimeSpan wait;
    bool incompleteDeadline;
    bool acquired;

    wait = ComputeSleepTime( nextDeadlineUtc, out incompleteDeadline );

    acquired = this.waiter.Wait( wait );

    if( acquired || incompleteDeadline )
    {
        // Either:
        // - We were manually woken up early by someone releasing the semaphore.
        // - The timeout expired, but that's because we didn't wait for the complete time.
        // 
        // Either way, the deadline didn't expire.
        return false;
    }
    else
    {
        // The deadline occurred. 
        return true;
    }
}

private TimeSpan ComputeSleepTime( DateTime nextDeadlineUtc, out bool incompleteDeadline )
{
    TimeSpan totalRemaining = nextDeadlineUtc - DateTime.UtcNow;

    if( totalRemaining.Ticks < 0 )
    {
        // Were already out of time.
        incompleteDeadline = false;
        return TimeSpan.FromTicks( 0 );
    }
    else if( totalRemaining.TotalHours <= 1.01 )
    {
        // Just sleep the whole of the remainder.
        incompleteDeadline = false;
        return totalRemaining;
    }
    else
    {
        // More than one hour to sleep. Sleep for at most one hour, but tell the sleeper that
        // there's still more time left.
        incompleteDeadline = true;
        return TimeSpan.FromHours( 1.0 );
    }
}

Upvotes: 0

Mark Redman
Mark Redman

Reputation: 24515

Keep a time variable that indicates the "Next" datetime to run.

In your loop just check if the current time of after that and run your code.. then reset the variable to the next daytime i.e. Now + 24 hours.

as another answer indicates the issue is with the line:

Console.Read();

which needs to be removed

Upvotes: 0

Paddy
Paddy

Reputation: 33857

I would second the many comments suggesting other, better suited methods of doing this, but I believe your problem is the lines with:

Console.Read();

From the documentation:

The Read method blocks its return while you type input characters; it terminates when you press the Enter key.

So it will block waiting for an entry that will never come.

Upvotes: 9

Related Questions