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 :)
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.
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
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 );
}
}
Related
.NET 6 introduced the PeriodicTimer.
I need to do something every minute, at the top of the minute. For example: 09:23:00, 09:24:00, 09:25:00, ...
But with a one minute period - new PeriodicTimer(TimeSpan.FromMinutes(1)) - and starting at 09:23:45, I will get "ticks" at: 09:24:45, 09:25:45, 09:26:45, ...
So it's dependent on the start time.
My workaround is a one-second period, and a check that the current time has seconds equal to 0. Another workaround is to wait for the next minute and then start the timer. Both approaches work but are fiddly and use too fine a resolution.
Is there a built-in or better way to trigger at the top of the minute rather than one-minute-after-start?
AFAIK there is nothing like this available in the standard .NET libraries. And I don't think that it's likely to be added any time soon. My suggestion is to use the third party Cronos library, that does a good job at calculating time intervals¹. You can find a usage example here, by Stephen Cleary. What this library does is to take a DateTime and a Cron expression as input, and calculate the next DateTime that satisfies this expression. It is just a DateTime calculator, not a scheduler.
If you want to get fancy you could include the functionality of the Cronos library in a custom PeriodicTimer-like component, like the one below:
using Cronos;
public sealed class CronosPeriodicTimer : IDisposable
{
private readonly CronExpression _cronExpression; // Also used as the locker
private PeriodicTimer _activeTimer;
private bool _disposed;
private static readonly TimeSpan _minDelay = TimeSpan.FromMilliseconds(500);
public CronosPeriodicTimer(string expression, CronFormat format)
{
_cronExpression = CronExpression.Parse(expression, format);
}
public async ValueTask<bool> WaitForNextTickAsync(
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
PeriodicTimer timer;
lock (_cronExpression)
{
if (_disposed) return false;
if (_activeTimer is not null)
throw new InvalidOperationException("One consumer at a time.");
DateTime utcNow = DateTime.UtcNow;
DateTime? utcNext = _cronExpression.GetNextOccurrence(utcNow + _minDelay);
if (utcNext is null)
throw new InvalidOperationException("Unreachable date.");
TimeSpan delay = utcNext.Value - utcNow;
Debug.Assert(delay > _minDelay);
timer = _activeTimer = new(delay);
}
try
{
// Dispose the timer after the first tick.
using (timer)
return await timer.WaitForNextTickAsync(cancellationToken)
.ConfigureAwait(false);
}
finally { Volatile.Write(ref _activeTimer, null); }
}
public void Dispose()
{
PeriodicTimer activeTimer;
lock (_cronExpression)
{
if (_disposed) return;
_disposed = true;
activeTimer = _activeTimer;
}
activeTimer?.Dispose();
}
}
Apart from the constructor, the CronosPeriodicTimer class has identical API and behavior with the PeriodicTimer class. You could use it like this:
var timer = new CronosPeriodicTimer("0 * * * * *", CronFormat.IncludeSeconds);
//...
await timer.WaitForNextTickAsync();
The expression 0 * * * * * means "on the 0 (zero) second of every minute, of every hour, of every day of the month, of every month, and of every day of the week."
You can find detailed documentation about the format of the Cron expressions here.
The 500 milliseconds _minDelay has the intention to prevent the remote possibility of the timer ticking twice by mistake. Also because the PeriodicTimer class has a minimum period of 1 millisecond.
For an implementation that uses the Task.Delay method instead of the PeriodicTimer class, and so it can be used by .NET versions previous than 6.0, you can look at the 3rd revision of this answer.
¹ With the caveat that the Cronos library is currently capped to the year 2099 (version 0.7.1).
For completeness, here are the workarounds mentioned in my question.
Tick every second, and wait for top-of-the-minute:
var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(cancellationToken)) {
if (DateTime.UtcNow.Second == 0)
await DoSomething();
}
Delay till top-of-the-minute, then tick every minute:
var delay = (60 - DateTime.UtcNow.Second) * 1000; // take milliseconds into account to improve start-time accuracy
await Task.Delay(delay);
var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (await timer.WaitForNextTickAsync(cancellationToken)) {
await DoSomething();
}
Some notes about accuracy:
This will never be perfectly accurate, as discussed in the comments above. But it's good enough in many cases.
Small clock drifts will occur and make the per-minute timer more and more inaccurate, and this will only be fixed when the server is restarted. But the per-second timer "self-corrects" on every tick, so although it's "heavier", it will be more accurate over time.
The per-second timer can sometimes lose a tick or get a double tick (see comments above). The per-minute timer won't lose ticks, but they may be inaccurate. Whether this matters is something for you to consider.
I have a while loop that waits for a local setting value to no longer be null. In the event that something is borked and the value remains null, is there some sort of best practice for how you should break out of the loop? Right now I just have it counting up to 100 and if it's looped 100 times to break the loop:
int i = 0;
while (ApplicationData.Current.LocalSettings.Values["MediaInfoSaved"] == null)
{
await Task.Delay(15);
i++;
if(i >= 100)
{
break;
}
}
The loop is waiting for a process in a full trust helper to finish and return it's result. The process can take a while to complete sometimes (0.1-1 second).
Put the value (represented as 100 in your code) in a config file so it's easily changed if needed. If issues pop up later you can adjust it as needed.
If the process is expected to take up to a second, then wait for just a second. No need to loop and increment a counter:
private async Task<object> GetLocalSettingAsync()
{
const string SettingName = "MediaInfoSaved";
// Try to get the value immediately:
var setting = ApplicationData.Current.LocalSettings.Values[SettingName];
if (setting != null)
return setting;
// Wait for one second only.
await Task.Delay(TimeSpan.FromSeconds(1));
// Return either a value or null.
return ApplicationData.Current.LocalSettings.Values[SettingName];
}
If you want to exit as fast as possible but wait for up to a second, then spin in a loop and poll the property:
private async Task<object> GetLocalSettingAsync()
{
const string SettingName = "MediaInfoSaved";
DateTime start = DateTime.UtcNow;
object setting;
do
{
setting = ApplicationData.Current.LocalSettings.Values[SettingName];
if (setting != null)
break;
//optionally wait here:
await Task.Delay(15);
} while (DateTime.UtcNow.Subtract(start).TotalSeconds < 1);
return setting;
}
I am afraid it doesn't get any better unless the object that you are monitoring raises some kind of event or callback when there is data available.
It is very common practice to implement a timeout, where you wait a specified amount of time (if you have an idea how long the process should be taking) before breaking out of the loop. This is basically what you have implemented. If you think it shouldn't take more than 1 second to complete, make sure it breaks the loop after 1.5-2 seconds. There may be more effective ways to implement this, like using the actual time within the system.
I have a Windows service, developed in C#, which does some calculation on data at equal intervals of time say 30 mins. It fetches the data from database and calls a method CalcData() which does some business logic calculations.
class Program
{
static void Main(string[] args)
{
try
{
AutoCalExecution ae = new AutoCalExecution();
ae.FetchData();
ae.CalData();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
class AutoCalExecution
{
public void FetchData()
{
// fetch data from db
}
public void CalData()
{
line1;
line2;
line3;
line4; // this line has som expression which actually does the calculation.
line5;
}
}
I have given the template of the code which I'm using. In CalData(), line4 is where the calculation is happening. The calculation typically takes 10 mins to be done. So line4 is executed for 10mins.
There are some scenarios where the calculation might take more than 10 mins. In that case I want to cancel the execution and go to line5 after certain amount of time, say 15 mins.
To summarize I want to set a timeout time for line4 for 15 mins(which can be configured based in requirement), If it doesn't finish with in 15 mins, it has to stop and come to line5.
public void CalData()
{
line1;
line2;
line3;
if ( set time to 15 mins exceeds){
line4; // this line has some expression which actually does the calculation.
}
else
{
Log (Line4 execution did not complete in stipulated time);
}
line5;
}
How do I set that condition in C#?
Update
This is something I tried:
var task = Task.Run(() => CalData());
if (task.Wait(TimeSpan.FromMinutes(Convert.ToDouble(timeout))))
{
if (task.Result)
{
log.Info("Completed");
}
else
{
log.Error("Not successful");
}
}
But the problem here is I want line5 to get executed in this method if line 4 doesn't finish. Is there a way I can write this similar code for a piece of code/snippet, instead of whole method?
Make line4 into a task.
Make the task cancellable by a cancellation token.
Use a cancellation token which cancels itself after 10mins (configured time).
https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.cancelafter?view=netframework-4.8
https://binary-studio.com/2015/10/23/task-cancellation-in-c-and-things-you-should-know-about-it/
I think you want something like this:
var work = Task.Run(() => line4);
if (work.Wait(TimeSpan.FromMinutes(10)))
{
// Work completed within the timeout
}
else
{
// Work did not complete within the timeout
}
Note that this will not actually stop the 'DoWork' code from running, it will continue on a worker thread until it is done. Also note that using 'Wait' risks deadlocks if used improperly, see Don't Block on Async Code.
If you actually want to cancel the processing you should give DoWork a cancellationToken and make the processing abort when the token is cancelled. There are also solutions to abort a running thread, but this is not recommended.
I am creating a program to perform certain tasks every "x" minute.
For example:
Task A: every 10 minutes (this task takes 5 minutes to complete)
Task B: every 10 minutes (this task takes 5 minutes to complete)
Task C: every 1 hour (this task takes 30 mins to complete)
I'm having a bit of trouble getting the program to wait for each task, I'm using Sleep () to hold the wait, however, the time is not being accurate.
I'm running the process on separate threads.
public void Job() {
while (true) {
MyClass bot = null;
try {
bot = new MyClass(Accounts.FirstOrDefault());
bot.TwoCaptchaApi = "...";
bot.GridView = gridLogs;
bot.Login();
if (Task1)
bot.Task1();
if (Task2)
bot.Task2();
if (Task3)
bot.Task();
bot.Quit();
bot.Log("Aguardando....");
} catch (ThreadAbortException) {
bot ? .Quit();
} catch (Exception) {
bot ? .Quit();
Job();
}
Thread.Sleep(Convert.ToInt32(TimeSpan.FromMinutes(30).TotalMilliseconds));
bot.Log("Tempo de espera terminado!");
}
}
This means that I am using is not working well ... I do not know if using DateTime () would be the best way ... in case initialize a global variable and store the date and time of the last activity of each task
DateTime LastActivity = DateTime.Now;
if (LastActivity.AddHours (1)> DateTime.Now)
This question already has answers here:
How do you add a timer to a C# console application
(12 answers)
Closed 5 years ago.
I have a while loop that runs for a long number of times. I have some writelines in there that serve as debug statements. I use a verbosity flag to determine when I want to see those statements written to the console. Is there a way I can also specify to output every x milliseconds , instead of all the time.
while
{
//logic here
if(verboseMode)
Console.Writeline("some status message")
}
With the way the code is right now, the writeline executes all the time when verboseMode is set to true. What id like to do is output the line if verboseMode is set to true and the last time I output something it was x milliseconds ago
You can use a Timer or just keep track of when you last wrote output. The Timer is probably preferable because your main functionality won't block it from running whereas the other will.
I used random just to simulate the fact that the while loop won't always run in the same amount of time to demonstrate the difference between the approaches.
var r = new Random();
var t = new System.Timers.Timer() { Interval = 1500 };
t.Elapsed += (s, e) =>
Console.WriteLine(DateTime.Now.TimeOfDay);
t.Start();
while (true)
{
Thread.Sleep(r.Next(500, 1000));
Console.WriteLine("doing stuff");
}
var r = new Random();
var prev = DateTime.Now;
var interval = 1500;
while (true)
{
Thread.Sleep(r.Next(500, 1000));
Console.WriteLine("doing stuff");
var now = DateTime.Now;
if (prev.AddMilliseconds(interval) >= now)
{
prev = DateTime.Now;
Console.WriteLine(DateTime.Now.TimeOfDay);
}
}
What you ask for is rate limiting. I wrote this code originally for Multithreading, but it should get you the idea:
integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);
while(true){
if(DateTime.Now >= dueTime){
//insert code here
//Update next dueTime
dueTime = DateTime.Now.AddMillisconds(interval);
}
else{
//Just yield to not tax out the CPU
Thread.Sleep(1);
}
}
Note that DateTime is not nearly as accurate as the type is precise. Often the smalest difference tracked is 16 ms or so. But then again, 16 ms would get you around 60 changes per seconds, wich is propably to top adivseable writing/updating speed anyway.
If you don't care much about precision you can get away with running the while loop on a different thread using Task.Run:
var source = new CancellationTokenSource();
var task = Task.Run(() =>
{
while (!source.Token.IsCancellationRequested)
{
DoSomething();
await Task.Delay(500, source.Token);
}
});
// If you want to cancel the loop
source.Cancel();
task.Wait(); // or 'await task;' if you're in an async method