I have below code :
public async Task StartAsync(CancellationToken cancellationToken)
{
var cronExpressionVal = new Timer(async e => await GetCronExpression(cancellationToken), null, TimeSpan.Zero, new TimeSpan(0, 5, 0));
}
What I am trying to achieve is, GetCronExpression method should run at every 5 minutes.
But my problem is, when we first time run programme so it is coming in StartAsync method.
And it execute successfully.
Now it is not coming again in this method so my GetCronExpression method is not calling at every 5 minutes.
So my question is where should I put this GetCronExpression method call so it execute at every 5 minutes.
What I am trying to achieve is, GetCronExpression method should run at
every 5 minutes.
Well, couple of ways to handle this. However, the most efficient and easiest way to meet your requirement is to use PeriodicTimer. Importantly, you don't need to think where should you keep this. You can call your method every 5 minutes time interval from everywhere. It could be within middleware or any custom class , repository, even from controller.
Implementation Using Middleware:
public class AutoTimerMiddleware
{
private readonly RequestDelegate _next;
public AutoTimerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
int counter = 0;
while (await timer.WaitForNextTickAsync())
{
counter++;
// if (counter > 5) break;
CallThisMethodEvery5Second(counter);
}
// Move forward into the pipeline
await _next(httpContext);
}
public void CallThisMethodEvery5Second(int counter)
{
Console.WriteLine("Current counter: {0} Last Fired At: {1}", counter, DateTime.Now);
}
}
Note: When use in middleware, please register in program.cs file as following
app.UseMiddleware<AutoTimerMiddleware>();
Output:
Implementation Using Any Custom Class/Anywhere:
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
int counter = 0;
while (await timer.WaitForNextTickAsync())
{
counter++;
if (counter > 5) break;
CallThisMethodEvery5Second(counter);
}
Note: You will call your method GetCronExpression within the WaitForNextTickAsync so that will be called at your given time frame. For the demo, I am calling this in every 5 seconds.
Method To Call:
public void CallThisMethodEvery5Second(int counter)
{
Console.WriteLine("Current counter: {0} Last Fired At: {1}",counter, DateTime.Now);
}
Output:
You have to keep reference to timer otherwise it will be garbage collected:
As long as you are using a Timer, you must keep a reference to it. As with any managed object, a Timer is subject to garbage collection when there are no references to it. The fact that a Timer is still active does not prevent it from being collected.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-7.0#remarks
If it's not the case, I suggest including more context.
I've searched on SO and found answers about Quartz.net. But it seems to be too big for my project. I want an equivalent solution, but simpler and (at best) in-code (no external library required). How can I call a method daily, at a specific time?
I need to add some information about this:
the simplest (and ugly) way to do this, is check the time every second/minute and call the method, at right time
I want a more-effective way to do this, no need to check the time constantly, and I have control about whether the job is done a not. If the method fails (because of any problems), the program should know to write to log/send a email. That's why I need to call a method, not schedule a job.
I found this solution Call a method at fixed time in Java in Java. Is there a similar way in C#?
EDIT: I've done this. I added a parameter into void Main(), and created a bat (scheduled by Windows Task Scheduler) to run the program with this parameter. The program runs, does the job, and then exits. If a job fails, it's capable of writing log and sending email. This approach fits my requirements well :)
Create a console app that does what you're looking for
Use the Windows "Scheduled Tasks" functionality to have that console app executed at the time you need it to run
That's really all you need!
Update: if you want to do this inside your app, you have several options:
in a Windows Forms app, you could tap into the Application.Idle event and check to see whether you've reached the time in the day to call your method. This method is only called when your app isn't busy with other stuff. A quick check to see if your target time has been reached shouldn't put too much stress on your app, I think...
in a ASP.NET web app, there are methods to "simulate" sending out scheduled events - check out this CodeProject article
and of course, you can also just simply "roll your own" in any .NET app - check out this CodeProject article for a sample implementation
Update #2: if you want to check every 60 minutes, you could create a timer that wakes up every 60 minutes and if the time is up, it calls the method.
Something like this:
using System.Timers;
const double interval60Minutes = 60 * 60 * 1000; // milliseconds to one hour
Timer checkForTime = new Timer(interval60Minutes);
checkForTime.Elapsed += new ElapsedEventHandler(checkForTime_Elapsed);
checkForTime.Enabled = true;
and then in your event handler:
void checkForTime_Elapsed(object sender, ElapsedEventArgs e)
{
if (timeIsReady())
{
SendEmail();
}
}
I created a simple scheduler that is easy to use and you do not need to use external library. TaskScheduler is a singleton that keeps references on the timers so timers will not be garbage collected, it can schedule multiple tasks. You can set the first run (hour and minute), if at the time of scheduling this time is over scheduling start on the next day this at that time. But it is easy to customize the code.
Scheduling a new task is so simple. Example: At 11:52 the first task is for every 15 secunds, the second example is for every 5 secunds. For daily execution set 24 to the 3 parameter.
TaskScheduler.Instance.ScheduleTask(11, 52, 0.00417,
() =>
{
Debug.WriteLine("task1: " + DateTime.Now);
//here write the code that you want to schedule
});
TaskScheduler.Instance.ScheduleTask(11, 52, 0.00139,
() =>
{
Debug.WriteLine("task2: " + DateTime.Now);
//here write the code that you want to schedule
});
My debug window:
task2: 07.06.2017 11:52:00
task1: 07.06.2017 11:52:00
task2: 07.06.2017 11:52:05
task2: 07.06.2017 11:52:10
task1: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:20
task2: 07.06.2017 11:52:25
...
Just add this class to your project:
public class TaskScheduler
{
private static TaskScheduler _instance;
private List<Timer> timers = new List<Timer>();
private TaskScheduler() { }
public static TaskScheduler Instance => _instance ?? (_instance = new TaskScheduler());
public void ScheduleTask(int hour, int min, double intervalInHour, Action task)
{
DateTime now = DateTime.Now;
DateTime firstRun = new DateTime(now.Year, now.Month, now.Day, hour, min, 0, 0);
if (now > firstRun)
{
firstRun = firstRun.AddDays(1);
}
TimeSpan timeToGo = firstRun - now;
if (timeToGo <= TimeSpan.Zero)
{
timeToGo = TimeSpan.Zero;
}
var timer = new Timer(x =>
{
task.Invoke();
}, null, timeToGo, TimeSpan.FromHours(intervalInHour));
timers.Add(timer);
}
}
Whenever I build applications that require such functionality, I always use the Windows Task Scheduler through a simple .NET library that I found.
Please see my answer to a similar question for some sample code and more explanation.
As others have said you can use a console app to run when scheduled. What others haven't said is that you can this app trigger a cross process EventWaitHandle which you are waiting on in your main application.
Console App:
class Program
{
static void Main(string[] args)
{
EventWaitHandle handle =
new EventWaitHandle(true, EventResetMode.ManualReset, "GoodMutexName");
handle.Set();
}
}
Main App:
private void Form1_Load(object sender, EventArgs e)
{
// Background thread, will die with application
ThreadPool.QueueUserWorkItem((dumby) => EmailWait());
}
private void EmailWait()
{
EventWaitHandle handle =
new EventWaitHandle(false, EventResetMode.ManualReset, "GoodMutexName");
while (true)
{
handle.WaitOne();
SendEmail();
handle.Reset();
}
}
Here's a way to do this using TPL. No need to create/dispose of a timer, etc:
void ScheduleSomething()
{
var runAt = DateTime.Today + TimeSpan.FromHours(16);
if (runAt <= DateTime.Now)
{
DoSomething();
}
else
{
var delay = runAt - DateTime.Now;
System.Threading.Tasks.Task.Delay(delay).ContinueWith(_ => DoSomething());
}
}
void DoSomething()
{
// do somethig
}
The best method that I know of and probably the simplest is to use the Windows Task Scheduler to execute your code at a specific time of day or have you application run permanently and check for a particular time of day or write a windows service that does the same.
I know this is old but how about this:
Build a timer to fire at startup that calculates time to next run time. At the first call of the runtime, cancel the first timer and start a new daily timer. change daily to hourly or whatever you want the periodicity to be.
This little program should be the solution ;-)
I hope this helps everyone.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DailyWorker
{
class Program
{
static void Main(string[] args)
{
var cancellationSource = new CancellationTokenSource();
var utils = new Utils();
var task = Task.Run(
() => utils.DailyWorker(12, 30, 00, () => DoWork(cancellationSource.Token), cancellationSource.Token));
Console.WriteLine("Hit [return] to close!");
Console.ReadLine();
cancellationSource.Cancel();
task.Wait();
}
private static void DoWork(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Console.Write(DateTime.Now.ToString("G"));
Console.CursorLeft = 0;
Task.Delay(1000).Wait();
}
}
}
public class Utils
{
public void DailyWorker(int hour, int min, int sec, Action someWork, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var dateTimeNow = DateTime.Now;
var scanDateTime = new DateTime(
dateTimeNow.Year,
dateTimeNow.Month,
dateTimeNow.Day,
hour, // <-- Hour when the method should be started.
min, // <-- Minutes when the method should be started.
sec); // <-- Seconds when the method should be started.
TimeSpan ts;
if (scanDateTime > dateTimeNow)
{
ts = scanDateTime - dateTimeNow;
}
else
{
scanDateTime = scanDateTime.AddDays(1);
ts = scanDateTime - dateTimeNow;
}
try
{
Task.Delay(ts).Wait(token);
}
catch (OperationCanceledException)
{
break;
}
// Method to start
someWork();
}
}
}
}
I just recently wrote a c# app that had to restart daily. I realize this question is old but I don't think it hurts to add another possible solution. This is how I handled daily restarts at a specified time.
public void RestartApp()
{
AppRestart = AppRestart.AddHours(5);
AppRestart = AppRestart.AddMinutes(30);
DateTime current = DateTime.Now;
if (current > AppRestart) { AppRestart = AppRestart.AddDays(1); }
TimeSpan UntilRestart = AppRestart - current;
int MSUntilRestart = Convert.ToInt32(UntilRestart.TotalMilliseconds);
tmrRestart.Interval = MSUntilRestart;
tmrRestart.Elapsed += tmrRestart_Elapsed;
tmrRestart.Start();
}
To ensure your timer is kept in scope I recommend creating it outside of the method using System.Timers.Timer tmrRestart = new System.Timers.Timer() method. Put the method RestartApp() in your form load event. When the application launches it will set the values for AppRestart if current is greater than the restart time we add 1 day to AppRestart to ensure the restart happens on time and that we don't get an exception for putting a negative value into the timer. In the tmrRestart_Elapsed event run whatever code you need ran at that specific time. If your application restarts on it's own you don't necessarily have to stop the timer but it doesn't hurt either, If the application does not restart simply call the RestartApp() method again and you will be good to go.
How about a 3 liner?
DateTime startTime = DateTime.Today.AddDays(1).AddHours(8).AddMinutes(30); // Today starts at midnight, so add the number of days, hours and minutes until the desired start time, which in this case is the next day at 8:30 a.m.
TimeSpan waitFor = startTime - DateTime.Now; // Calcuate how long it is until the start time
await Task.Delay(waitFor); // Wait until the start time
If you want an executable to run, use Windows Scheduled Tasks. I'm going to assume (perhaps erroneously) that you want a method to run in your current program.
Why not just have a thread running continuously storing the last date that the method was called?
Have it wake up every minute (for example) and, if the current time is greater than the specified time and the last date stored is not the current date, call the method then update the date.
It may just be me but it seemed like most of these answers were not complete or would not work correctly. I made something very quick and dirty. That being said not sure how good of an idea it is to do it this way, but it works perfectly every time.
while (true)
{
if(DateTime.Now.ToString("HH:mm") == "22:00")
{
//do something here
//ExecuteFunctionTask();
//Make sure it doesn't execute twice by pausing 61 seconds. So that the time is past 2200 to 2201
Thread.Sleep(61000);
}
Thread.Sleep(10000);
}
I found this very useful:
using System;
using System.Timers;
namespace ScheduleTimer
{
class Program
{
static Timer timer;
static void Main(string[] args)
{
schedule_Timer();
Console.ReadLine();
}
static void schedule_Timer()
{
Console.WriteLine("### Timer Started ###");
DateTime nowTime = DateTime.Now;
DateTime scheduledTime = new DateTime(nowTime.Year, nowTime.Month, nowTime.Day, 8, 42, 0, 0); //Specify your scheduled time HH,MM,SS [8am and 42 minutes]
if (nowTime > scheduledTime)
{
scheduledTime = scheduledTime.AddDays(1);
}
double tickTime = (double)(scheduledTime - DateTime.Now).TotalMilliseconds;
timer = new Timer(tickTime);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("### Timer Stopped ### \n");
timer.Stop();
Console.WriteLine("### Scheduled Task Started ### \n\n");
Console.WriteLine("Hello World!!! - Performing scheduled task\n");
Console.WriteLine("### Task Finished ### \n\n");
schedule_Timer();
}
}
}
Try to use Windows Task Scheduler. Create an exe which is not prompting for any user inputs.
https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page
Rather than setting a time to run every second of every 60 minutes you can calculate the time remaining and set the timer to half (or some other fraction) of this. This way your not checking the time as much but also maintianing a degree of accurcy as the timer interval reduces the closer you get to your target time.
For example if you wanted to do something 60 minutes from now the timers intervals would be aproximatly:
30:00:00, 15:00:00, 07:30:00, 03:45:00, ... , 00:00:01, RUN!
I use the code below to automatically restart a service once a day. I use a thread becuase I have found timers to be unreliable over long periods, while this is more costly in this example it is the only one created for this purpose so this dosn't matter.
(Converted from VB.NET)
autoRestartThread = new System.Threading.Thread(autoRestartThreadRun);
autoRestartThread.Start();
...
private void autoRestartThreadRun()
{
try {
DateTime nextRestart = DateAndTime.Today.Add(CurrentSettings.AutoRestartTime);
if (nextRestart < DateAndTime.Now) {
nextRestart = nextRestart.AddDays(1);
}
while (true) {
if (nextRestart < DateAndTime.Now) {
LogInfo("Auto Restarting Service");
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = string.Format("/C net stop {0} && net start {0}", "\"My Service Name\"");
p.StartInfo.LoadUserProfile = false;
p.StartInfo.UseShellExecute = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.CreateNoWindow = true;
p.Start();
} else {
dynamic sleepMs = Convert.ToInt32(Math.Max(1000, nextRestart.Subtract(DateAndTime.Now).TotalMilliseconds / 2));
System.Threading.Thread.Sleep(sleepMs);
}
}
} catch (ThreadAbortException taex) {
} catch (Exception ex) {
LogError(ex);
}
}
Note I have set a mininum interval of 1000 ms, this could be increaded, reduced or removed depending upon the accurcy you require.
Remember to also stop your thread/timer when your application closes.
I have a simple approach to this. This creates a 1 minute delay before the action happens. You could add seconds as well to make the Thread.Sleep(); shorter.
private void DoSomething(int aHour, int aMinute)
{
bool running = true;
while (running)
{
Thread.Sleep(1);
if (DateTime.Now.Hour == aHour && DateTime.Now.Minute == aMinute)
{
Thread.Sleep(60 * 1000); //Wait a minute to make the if-statement false
//Do Stuff
}
}
}
24 hours times
var DailyTime = "16:59:00";
var timeParts = DailyTime.Split(new char[1] { ':' });
var dateNow = DateTime.Now;
var date = new DateTime(dateNow.Year, dateNow.Month, dateNow.Day,
int.Parse(timeParts[0]), int.Parse(timeParts[1]), int.Parse(timeParts[2]));
TimeSpan ts;
if (date > dateNow)
ts = date - dateNow;
else
{
date = date.AddDays(1);
ts = date - dateNow;
}
//waits certan time and run the code
Task.Delay(ts).ContinueWith((x) => OnTimer());
public void OnTimer()
{
ViewBag.ErrorMessage = "EROOROOROROOROR";
}
A simple example for one task:
using System;
using System.Timers;
namespace ConsoleApp
{
internal class Scheduler
{
private static readonly DateTime scheduledTime =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 10, 0, 0);
private static DateTime dateTimeLastRunTask;
internal static void CheckScheduledTask()
{
if (dateTimeLastRunTask.Date < DateTime.Today && scheduledTime.TimeOfDay < DateTime.Now.TimeOfDay)
{
Console.WriteLine("Time to run task");
dateTimeLastRunTask = DateTime.Now;
}
else
{
Console.WriteLine("not yet time");
}
}
}
internal class Program
{
private static Timer timer;
static void Main(string[] args)
{
timer = new Timer(5000);
timer.Elapsed += OnTimer;
timer.Start();
Console.ReadLine();
}
private static void OnTimer(object source, ElapsedEventArgs e)
{
Scheduler.CheckScheduledTask();
}
}
}
Solution with System.Threading.Timer:
private void nameOfMethod()
{
//do something
}
/// <summary>
/// run method at 22:00 every day
/// </summary>
private void runMethodEveryDay()
{
var runAt = DateTime.Today + TimeSpan.FromHours(22);
if(runAt.Hour>=22)
runAt = runAt.AddDays(1.00d); //if aplication is started after 22:00
var dueTime = runAt - DateTime.Now; //time before first run ;
long broj3 = (long)dueTime.TotalMilliseconds;
TimeSpan ts2 = new TimeSpan(24, 0, 1);//period of repeating method
long broj4 = (long)ts2.TotalMilliseconds;
timer2 = new System.Threading.Timer(_ => nameOfMethod(), null, broj3, broj4);
}
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
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 );
}
}
The maximum duration a Task.Delay can be told to delay is int.MaxValue milliseconds. What is the cleanest way to create a Task which will delay beyond that time?
// Fine.
await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue));
// ArgumentOutOfRangeException
await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue + 1L));
You can't achieve that using a single Task.Delay since it internally uses a System.Threading.Timer that only accepts an int.
However you can do that using multiple waits one after the other. Here's the cleanest way:
static async Task Delay(long delay)
{
while (delay > 0)
{
var currentDelay = delay > int.MaxValue ? int.MaxValue : (int) delay;
await Task.Delay(currentDelay);
delay -= currentDelay;
}
}
You can easily write a method to break it down into smaller delays:
private static readonly TimeSpan FullDelay = TimeSpan.FromMilliseconds(int.MaxValue);
private static async Task LongDelay(TimeSpan delay)
{
long fullDelays = delay.Ticks / FullDelay.Ticks;
TimeSpan remaining = delay;
for(int i = 0; i < fullDelays; i++)
{
await Task.Delay(FullDelay);
remaining -= FullDelay;
}
await Task.Delay(remaining);
}
You can delay multiple times. For example:
static async Task LongDelay(long milliseconds)
{
if (milliseconds < 0)
{
throw new ArgumentOutOfRangeException();
}
if (milliseconds == 0)
{
return;
}
int iterations = (milliseconds - 1) / int.MaxValue;
while (iterations-- > 0)
{
await Task.Delay(int.MaxValue);
milliseconds -= int.MaxValue;
}
await Task.Delay(milliseconds);
}
That said, int.MaxValue milliseconds is a really long time, almost 25 days! IMHO a much more important question is, is the Task.Delay() method really the best solution for your scenario? Knowing more about why you are trying to wait for such a long period of time might help others offer you a better solution to the actual problem, instead of addressing this very specific need.
If you care about precision, you should be using Stopwatch rather than deviding delay by Int16.MaxValue chunks. This is how the below code is different from other answers:
private static async Task LongDelay(TimeSpan delay)
{
var st = new System.Diagnostics.Stopwatch();
st.Start();
while (true)
{
var remaining = (delay - st.Elapsed).TotalMilliseconds;
if (remaining <= 0)
break;
if (remaining > Int16.MaxValue)
remaining = Int16.MaxValue;
await Task.Delay(TimeSpan.FromMilliseconds(remaining));
}
}
UPDATE: According to #CoryNelson's comment, Stopwatch is not good enough for long laps. If so, it's possible to simply use DateTime.UtcNow:
private static async Task LongDelay(TimeSpan delay)
{
var start = DateTime.UtcNow;
while (true)
{
var remaining = (delay - (DateTime.UtcNow - start)).TotalMilliseconds;
if (remaining <= 0)
break;
if (remaining > Int16.MaxValue)
remaining = Int16.MaxValue;
await Task.Delay(TimeSpan.FromMilliseconds(remaining));
}
}
As of .NET 6, the maximum valid TimeSpan delay value of the Task.Delay method is now 4,294,967,294 milliseconds (0xFFFFFFFE, or UInt32.MaxValue - 1), which is approximately 49 days and 17 hours. It is the same limit with the dueTime/period arguments of the System.Threading.Timer constructor, on which the Task.Delay is based internally.
Related GitHub issue: Task.Delay actual accepted delay is twice the documented value
Improved on #i3arnon version, which use multiple waits (delays) one after the other.:
Added support for optional cancellation token.
Added support for TimeSpan argument.
The method reproduces the same behavior as the original Task.Delay.
Parameter names are more consistent with the original method.
Added usage remark.
Note: Using the scheduler would be a good alternative to implement long delays.
/// <summary>Allow to delay Task for 292,471,209 years.</summary>
/// <remarks>Usage makes sense if the process won't be recycled before the delay expires.</remarks>
public static async Task LongDelay(
TimeSpan delay,
CancellationToken cancellationToken = default(CancellationToken)
) => await LongDelay((long)delay.TotalMilliseconds, cancellationToken).ConfigureAwait(false);
/// <summary>Allow to delay Task for 292,471,209 years.</summary>
/// <remarks>Usage makes sense if the process won't be recycled before the delay expires.</remarks>
public static async Task LongDelay(
long millisecondsDelay,
CancellationToken cancellationToken = default(CancellationToken)
) {
// Use 'do' to run Task.Delay at least once to reproduce the same behavior.
do {
var delay = (int)Math.Min(int.MaxValue, millisecondsDelay);
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
millisecondsDelay -= delay;
} while (millisecondsDelay > 0);
}
LongDelay example with CancellationToken:
// Create a token that auto-cancels after 10 seconds.
var source = new CancellationTokenSource(10000);
// Delay for 20 seconds.
try { LongDelay(20000, source.Token).Wait(); }
catch (TaskCanceledException) { } // Cancel silently.
catch (Exception) { throw; }
Edit: Applied suggestion by #TheodorZoulias
You cannot. Every overload will throw an ArgumentOutOfRange exception if you pass a value that would resolve to a greater number of milliseconds than Int32.MaxValue. This is true even for the TimeSpan overload (MSDN).
The best you could do is await twice:
await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue));
await Task.Delay(TimeSpan.FromMilliseconds(20));