I want to create a background worker for a WinForm that triggers code whenever midnight rolls by.
I have an idea of how to do it, but I'm pretty sure it's not the best way to do it.
while(1==1)
{
//if Datetime.Now == midnight, execute code
//sleep(1second)
}
Use a System.Timers.Timer and at application start up just calculate the difference between DateTime.Now and DateTime.Today.AddDays(0). Then set the interval for that amount.
I actually did something just like this recently:
public static class DayChangedNotifier
{
private static Timer timer;
static DayChangedNotifier()
{
timer = new Timer(GetSleepTime());
timer.Elapsed += (o, e) =>
{
OnDayChanged(DateTime.Now.DayOfWeek);
timer.Interval = this.GetSleepTime();
};
timer.Start();
SystemEvents.TimeChanged += new EventHandler(SystemEvents_TimeChanged);
}
private static void SystemEvents_TimeChanged(object sender, EventArgs e)
{
timer.Interval = GetSleepTime();
}
private static double GetSleepTime()
{
var midnightTonight = DateTime.Today.AddDays(1);
var differenceInMilliseconds = (midnightTonight - DateTime.Now).TotalMilliseconds;
return differenceInMilliseconds;
}
private static void OnDayChanged(DayOfWeek day)
{
var handler = DayChanged;
if (handler != null)
{
handler(null, new DayChangedEventArgs(day));
}
}
public static event EventHandler<DayChangedEventArgs> DayChanged;
}
AND:
public class DayChangedEventArgs : EventArgs
{
public DayChangedEventArgs(DayOfWeek day)
{
this.DayOfWeek = day;
}
public DayOfWeek DayOfWeek { get; private set; }
}
Useage: DayChangedNotified.DayChanged += ....
Instead you could user a Timer and set the timer tick interval to be the time between Now() and midnight.
I have no idea why polling solutions were voted up when Microsoft solved this type of problem years ago by adding a windows service to handle timing. Just create a scheduled task to run the exe. No extra overhead.
you can use Quartz to schedule that. Maybe is like a cannon to kill a mosquito in this scenario, but that's is the only scheduling job framework i know and works excellent.
Don't use polling. Instead, set up a timer task, set it to fire at midnight, and add an event to process.
TimeSpan timeBetween = DateTime.Today.AddDays(1) - DateTime.Now;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
t.Interval = 1000 * timeBetween.Seconds;
t.Start();
I'm a little confuse about why you need a WinForm, will it be running at midnight? If all you need is some sort process to run, use the windows scheduler to run it at midnight. (On XP, but I believe Win server should be similar)Control Panel -> Scheduled Tasks -> Add Scheduled Task -> Fill out the wizard. Save you a lot of coding.
Related
I want to create a background worker for a WinForm that triggers code whenever midnight rolls by.
I have an idea of how to do it, but I'm pretty sure it's not the best way to do it.
while(1==1)
{
//if Datetime.Now == midnight, execute code
//sleep(1second)
}
Use a System.Timers.Timer and at application start up just calculate the difference between DateTime.Now and DateTime.Today.AddDays(0). Then set the interval for that amount.
I actually did something just like this recently:
public static class DayChangedNotifier
{
private static Timer timer;
static DayChangedNotifier()
{
timer = new Timer(GetSleepTime());
timer.Elapsed += (o, e) =>
{
OnDayChanged(DateTime.Now.DayOfWeek);
timer.Interval = this.GetSleepTime();
};
timer.Start();
SystemEvents.TimeChanged += new EventHandler(SystemEvents_TimeChanged);
}
private static void SystemEvents_TimeChanged(object sender, EventArgs e)
{
timer.Interval = GetSleepTime();
}
private static double GetSleepTime()
{
var midnightTonight = DateTime.Today.AddDays(1);
var differenceInMilliseconds = (midnightTonight - DateTime.Now).TotalMilliseconds;
return differenceInMilliseconds;
}
private static void OnDayChanged(DayOfWeek day)
{
var handler = DayChanged;
if (handler != null)
{
handler(null, new DayChangedEventArgs(day));
}
}
public static event EventHandler<DayChangedEventArgs> DayChanged;
}
AND:
public class DayChangedEventArgs : EventArgs
{
public DayChangedEventArgs(DayOfWeek day)
{
this.DayOfWeek = day;
}
public DayOfWeek DayOfWeek { get; private set; }
}
Useage: DayChangedNotified.DayChanged += ....
Instead you could user a Timer and set the timer tick interval to be the time between Now() and midnight.
I have no idea why polling solutions were voted up when Microsoft solved this type of problem years ago by adding a windows service to handle timing. Just create a scheduled task to run the exe. No extra overhead.
you can use Quartz to schedule that. Maybe is like a cannon to kill a mosquito in this scenario, but that's is the only scheduling job framework i know and works excellent.
Don't use polling. Instead, set up a timer task, set it to fire at midnight, and add an event to process.
TimeSpan timeBetween = DateTime.Today.AddDays(1) - DateTime.Now;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
t.Interval = 1000 * timeBetween.Seconds;
t.Start();
I'm a little confuse about why you need a WinForm, will it be running at midnight? If all you need is some sort process to run, use the windows scheduler to run it at midnight. (On XP, but I believe Win server should be similar)Control Panel -> Scheduled Tasks -> Add Scheduled Task -> Fill out the wizard. Save you a lot of coding.
I have a issue that I really dont know why it occurs at all. I wpf c# application that use a timer to start a backgroundworker, sometimes the backgroundworker start the task twice, and I don't know why. The code I use is this....
private void startScheduledTask()
{
// Timer settings and start
dpTimer.Interval = TimeSpan.FromMilliseconds(CalculateTimerInterval(CHECK_INTERVAL));
dpTimer.Tick += new EventHandler(StartScheduledActivity);
dpTimer.Start();
}
private void StartScheduledActivity(Object sender, EventArgs args)
{
// Timer tick has occured, start scheduled work
StartScheduledWork();
dpTimer.Interval = TimeSpan.FromMilliseconds(CalculateTimerInterval(CHECK_INTERVAL));
}
private void StartScheduledWork()
{
MyHeavyWorker = new System.ComponentModel.BackgroundWorker();
if ((!MyHeavyWorker.IsBusy) && (MyHeavyWorker != null))
{
MyHeavyWorker.WorkerReportsProgress = true;
MyHeavyWorker.WorkerSupportsCancellation = true;
MyHeavyWorker.ProgressChanged += MyHeavyWorker_ProgressChanged;
MyHeavyWorker.DoWork += MyHeavyWorker_DoWork;
MyHeavyWorker.RunWorkerCompleted += MyHeavyWorker_RunWorkerCompleted;
MyHeavyWorker.RunWorkerAsync();
}
}
private void MyHeavyWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// This method sometime run twice at a time
FetchSomeFiles();
}
public int CalculateTimerInterval(int minute)
{
if (minute <= 0)
{
minute = 60;
}
DateTime CurrTime = DateTime.Now;
DateTime now = DateTime.Now;
DateTime future = now.AddMinutes((minute - (now.Minute % minute))).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
TimeSpan interval = future - now;
NextExecutionTime = future.ToShortTimeString();
NextExecutionDateTime = NextExecutionTime.ToString();
return Convert.ToInt32(interval.TotalMilliseconds);
}
Can anyone see why the method FetchSomeFiles sometimes runs twice at the same time?
It's quite simply because you are each time initializing a new instance of your backgroundworker - so if your timer event occurs before the previous backgroundworker is done it will start a second time with another bg Worker instance. Keep your Backgroundworker reference on class level and initialize it only once.
Do the same thing with the eventhandlers you are adding - move them to the class constructor or to a method called once when your object is instanciated.
//Put this line on class level and only initialize it once.
MyHeavyWorker = new System.ComponentModel.BackgroundWorker();
//Call this once to initialize your Backgroundworker
public void InitializeBackgroundWorker()
{
MyHeavyWorker.WorkerReportsProgress = true;
MyHeavyWorker.WorkerSupportsCancellation = true;
MyHeavyWorker.ProgressChanged += MyHeavyWorker_ProgressChanged;
MyHeavyWorker.DoWork += MyHeavyWorker_DoWork;
MyHeavyWorker.RunWorkerCompleted += MyHeavyWorker_RunWorkerCompleted;
}
Then check for the MyHeavyWorker.IsBusy of your one and only instance to check if it is currently doing some work before deciding to call RunWorkerAsync().
Another method would also be to just stop your timer with dpTimer.Stop() in StartScheduledActivity before you launch your BackgroundWorker and call dpTimer.Start() again in MyHeavyWorker_RunWorkerCompleted. Of course you will have to reconsider how you would like to calculate your next interval since with this solution the countdown does start after your backgroundworker is done - which could be considerably later than the point of the start.
Check
if MyHeavyWorker.IsBusy
before starting the task inside the DoWork() Method. This method will check if DoWork() is still running and will not start another call of this method until it is finished
I wanted to start a Windows service to run a function everyday at specific time.
What method i should consider to implement this? Timer or using threads?
(1) On first start, Set _timer.Interval to the amount of milliseconds between the service start and schedule time. This sample set schedule time to 7:00 a.m. as _scheduleTime = DateTime.Today.AddDays(1).AddHours(7);
(2) On Timer_Elapsed, reset _timer.Interval to 24 hours (in milliseconds) if current interval is not 24 hours.
System.Timers.Timer _timer;
DateTime _scheduleTime;
public WinService()
{
InitializeComponent();
_timer = new System.Timers.Timer();
_scheduleTime = DateTime.Today.AddDays(1).AddHours(7); // Schedule to run once a day at 7:00 a.m.
}
protected override void OnStart(string[] args)
{
// For first time, set amount of seconds between current time and schedule time
_timer.Enabled = true;
_timer.Interval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;
_timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
}
protected void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// 1. Process Schedule Task
// ----------------------------------
// Add code to Process your task here
// ----------------------------------
// 2. If tick for the first time, reset next run to every 24 hours
if (_timer.Interval != 24 * 60 * 60 * 1000)
{
_timer.Interval = 24 * 60 * 60 * 1000;
}
}
Edit:
Sometimes people want to schedule the service to start at day 0, not tomorrow so they change DateTime.Today.AddDays(0).If they do that and set a time in the past it causes an error setting the Interval with a negative number.
//Test if its a time in the past and protect setting _timer.Interval with a negative number which causes an error.
double tillNextInterval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;
if (tillNextInterval < 0) tillNextInterval += new TimeSpan(24, 0, 0).TotalSeconds * 1000;
_timer.Interval = tillNextInterval;
Are you sure, you need a service, that runs only one time per day?
Maybe Windows Task Schedule will be better solution?
Good answer (I used your code), but one problem with this line:
_timer.Interval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;
If DateTime.now is later than scheduleTime, you will go negative and this will generate an exception when assigning to timer.Interval.
I used:
if (DateTime.now > scheduleTime)
scheduleTime = scheduleTime.AddHours(24);
Then do the subtraction.
Use Windows built in Task Scheduler (http://windows.microsoft.com/en-us/windows7/schedule-a-task) or Quartz.net.
Unless ... you have a service that's doing lots of other processing and needs to be running all the time in which case a Timer might be appropriate.
private static double scheduledHour = 10;
private static DateTime scheduledTime;
public WinService()
{
scheduledTime = DateTime.Today.AddHours(scheduledHour);//setting 10 am of today as scheduled time- service start date
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
if (scheduledTime < DateTime.Now)
{
TimeSpan span = now - DateTime.Now;
scheduledTime = scheduledTime.AddMilliseconds(span.Milliseconds).AddDays(1);// this will set scheduled time to 10 am of next day while correcting the milliseconds
//do the scheduled task here
}
}
You can do it with a thread and an event; a timer is not necessary.
using System;
using System.ServiceProcess;
using System.Threading;
partial class Service : ServiceBase
{
Thread Thread;
readonly AutoResetEvent StopEvent;
public Service()
{
InitializeComponent();
StopEvent = new AutoResetEvent(initialState: false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
StopEvent.Dispose();
components?.Dispose();
}
base.Dispose(disposing);
}
protected override void OnStart(string[] args)
{
Thread = new Thread(ThreadStart);
Thread.Start(TimeSpan.Parse(args[0]));
}
protected override void OnStop()
{
if (!StopEvent.Set())
Environment.FailFast("failed setting stop event");
Thread.Join();
}
void ThreadStart(object parameter)
{
while (!StopEvent.WaitOne(Timeout(timeOfDay: (TimeSpan)parameter)))
{
// do work here...
}
}
static TimeSpan Timeout(TimeSpan timeOfDay)
{
var timeout = timeOfDay - DateTime.Now.TimeOfDay;
if (timeout < TimeSpan.Zero)
timeout += TimeSpan.FromDays(1);
return timeout;
}
}
if it is one in a day, why you don't use task scheduler?
windows service is useful when you want run task many time in minute. so if you want to run a program in a specific time, its better to use task scheduler and set event of task scheduler on a specific time in day.
I do many thing with task scheduler and its perfect.
you can set rout of program in task scheduler and set interval time to run it.
if you want to run a program every 5 min in a day still you can use task scheduler and its better way.
Is there any clever method out there to make my executeEveryDayMethod() execute once a day, without having to involve the Windows TaskScheduler?
I achieved this by doing the following...
Set up a timer that fires every 20 minutes (although the actual timing is up to you - I needed to run on several occasions throughout the day).
on each Tick event, check the system time. Compare the time to the scheduled run time for your method.
If the current time is less than the scheduled time, check a in some persistent storage to get the datetime value of the last time the method ran.
If the method last ran more than 24 hours ago, run the method, and stash the datetime of this run back to your data store
If the method last ran within the last 24 hours, ignore it.
HTH
*edit - code sample in C# :: Note : untested...
using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Timer t1 = new Timer();
t1.Interval = (1000 * 60 * 20); // 20 minutes...
t1.Elapsed += new ElapsedEventHandler(t1_Elapsed);
t1.AutoReset = true;
t1.Start();
Console.ReadLine();
}
static void t1_Elapsed(object sender, ElapsedEventArgs e)
{
DateTime scheduledRun = DateTime.Today.AddHours(3); // runs today at 3am.
System.IO.FileInfo lastTime = new System.IO.FileInfo(#"C:\lastRunTime.txt");
DateTime lastRan = lastTime.LastWriteTime;
if (DateTime.Now > scheduledRun)
{
TimeSpan sinceLastRun = DateTime.Now - lastRan;
if (sinceLastRun.Hours > 23)
{
doStuff();
// Don't forget to update the file modification date here!!!
}
}
}
static void doStuff()
{
Console.WriteLine("Running the method!");
}
}
}
Take a look at quartz.net. It is a scheduling library for .net.
More specifically take a look here.
If the time when it is run is not relevant and can be reset each time the program starts you can just set a timer, which is the easiest thing to do. If that's not acceptable it starts getting more complex, like the solution presented here and which still doesn't solve the persistence problem, you need to tackle that separately if you truly wish to do what Scheduled Tasks would. I'd really consider again if it's worth going through all the trouble to replicate a perfectly good existing functionality.
Here's a related question (Example taken from there).
using System;
using System.Timers;
public class Timer1
{
private static Timer aTimer = new System.Timers.Timer(24*60*60*1000);
public static void Main()
{
aTimer.Elapsed += new ElapsedEventHandler(ExecuteEveryDayMethod);
aTimer.Enabled = true;
Console.WriteLine("Press the Enter key to exit the program.");
Console.ReadLine();
}
// Specify what you want to happen when the Elapsed event is
// raised.
private static void ExecuteEveryDayMethod(object source, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}
}
public partial class Main : Form
{
public Main( ) // Windows Form is called Main
{
InitializeComponent( );
}
private void Main_Load( object sender, EventArgs e )
{
/*
This example uses a System.Windows.Forms Timer
This code allows you to schedule an event at any given time in one day.
In this example the timer will tick at 3AM.
*/
Int32 alarm = GetAlarmInMilliseconds( 3, 0, 0 ); // Milliseconds until 3:00 am.
timer_MessageCount.Interval = alarm; // Timer will tick at 3:00am.
timer_MessageCount.Start( );
}
private Int32 GetAlarmInMilliseconds(Int32 eventHour, Int32 eventMinute, Int32 eventSecond )
{
DateTime now = DateTime.Now;
DateTime eventTime = new DateTime( now.Year, now.Month, now.Day, eventHour, eventMinute, eventSecond );
TimeSpan ts;
if ( eventTime > now )
{
ts = eventTime - now;
}
else
{
eventTime = eventTime.AddDays( 1 );
ts = eventTime - now;
}
Console.WriteLine("Next alarm in: {0}", ts );
return ( Int32 ) ts.TotalMilliseconds;
}
static void DoSomething( )
{
Console.WriteLine( "Run your code here." );
}
private void timer_MessageCount_Tick( object sender, EventArgs e )
{
DoSomething( );
Int32 alarm = GetAlarmInMilliseconds( 3, 0, 0 ); // Next alarm time = 3AM
timer_MessageCount.Interval = alarm;
}
}
Suppose you have the daily time in _Settings.DataCleanupTime in Format "hh:mm:ss"
//note the namespace, there are 4 different timers in .NET
System.Threading.Timer _Timer;
DateTime now = DateTime.Now;
//convert "hh:mm:ss" to three integers
var dateparts = _Settings.DataCleanupTime.Split(new char[] { ':' }).Select(p => Convert.ToInt32(p)).ToArray();
DateTime firstTime = new DateTime(now.Year, now.Month, now.Day, dateparts[0], dateparts[1], dateparts[2]);
//e.g. firsttime is today at 2am and it is already 6am
if(firstTime < now)
{
//first run will be tomorrow
firstTime = firstTime.AddDays(1);
}
int delay = Convert.ToInt32((firstTime - now).TotalMilliseconds);
_Timer = new Timer(DoWork, state:null, delay, 3600 * 24 * 1000);
The signature of DoWork is:
public void DoWork(Object state)
To stop the timer just call:
_Timer.Dispose();
You could query time and run if your within some time frame, that way even if the machine goes off you'll call the method or use a timer like Vinko's suggestion.
But the better solution (akin to older CRON versions, so its a proven pattern) is to have some persistent data, with the cheapest solution I can think of right now being a blank file, check its last modified attribute, and if it hasn't been modified within the last 24 hours you touch it and run your method. This way you assure the method gets run first thing in the case the application is out for the weekend for example.
I've done this in C# before, but its was a year ago at another Job, so I don't have the code but it was about 20 lines (with comments and all) or so.
To run the job once daily between 7 and 8pm, i set up a timer with interval = 3600000 ms and then just execute the following code for timer tick.
private void timer1_Tick(object sender, EventArgs e)
{
//ensure that it is running between 7-8pm daily.
if (DateTime.Now.Hour == 19)
{
RunJob();
}
}
An hour window is fine for me. Extra granularity on time will require a smaller interval on the timer (60000 for a minute) and including minutes on the if.
eg
{
//ensure that it is running at 7:30pm daily.
if (DateTime.Now.Hour == 19 && DateTime.Now.Minute == 30)
{
RunJob();
}
}
If you only want to run it once a day and don't care when, this will work (will run just after midnight).
Declare a DateTime variable:
DateTime _DateLastRun;
In your startup, set the initial date value:
_DateLastRun = DateTime.Now.Date;
In the logic area where you want to check whether to perform the action:
if (_DateLastRun < DateTime.Now.Date)
{
// Perform your action
_DateLastRun= DateTime.Now.Date;
}
You can try this solution.
public Main()
{
StartService();
}
public async Task StartService(CancellationToken token = default(CancellationToken))
{
while (!token.IsCancellationRequested)
{
ExecuteFunction();
try
{
await Task.Delay(TimeSpan.FromDays(1), token);
}
catch (TaskCanceledException)
{
break;
}
}
}
public async Task ExecuteFunction()
{
...
}
Here is how you can do it if you're running a Windows Forms Application. But you need to configure a setting so that you can store the last date the event was fired. If you never intend to close the app you can just store the date as a static value.
Im using a timer to fire the event, as following:
private void tmrAutoBAK_Tick(object sender, EventArgs e)
{
if (BakDB.Properties.Settings.Default.lastFireDate != DateTime.Now.ToString("yyyy-MM-dd"))
{
tmrAutoBAK.Stop(); //STOPS THE TIMER IN CASE OF EVENTUAL MESSAGEBOXES.
createBakup(); //EVENT
BakDB.Properties.Settings.Default.lastFireDate = DateTime.Now.ToString("yyyy-MM-dd"); //STORING CURRENT DATE TO SETTINGS FILE.
BakDB.Properties.Settings.Default.Save(); //SAVING THE SETTING FILE.
tmrAutoBAK.Start(); //RESTARTING TIMER
}
}
This is my very simple solution to execute a method once once a day:
private static DateTime _LastAccessedTime;
private static void OnceADayCode() // method that you want access once a day
{
_LastAccessedTime = DateTime.Today;
}
public static void PublicMethod() // this can be a method called from outside
{
if (_LastAccessedTime != DateTime.Today)
{
OnceADayCode();
}
}
with the same logic you can also use:
private static DateTime _LastAccessedTime;
private static void OnceADayCode()
{
if (_LastAccessedTime.Today != DateTime.Today)
{
// code that you want access once a day
_LastAccessedTime = DateTime.Today;
}
}
I'm working on a little web crawler that will run in the system tray and crawl a web site every hour on the hour.
What is the best way to get .NET to raise an event every hour or some other interval to perform some task. For example I want to run an event every 20 minutes based on the time. The event would be raised at:
00:20
00:40
01:00
01:20
01:40
and so on. The best way I can think of to do this is by creating a loop on a thread, that constantly checks if the time is divisible by a given interval and raises a callback event if the time is reached. I feel like there has got to be a better way.
I'd use a Timer but I'd prefer something that follows a "schedule" that runs on the hour or something along those lines.
Without setting up my application in the windows task scheduler is this possible?
UPDATE:
I'm adding my algorithm for calculating the time interval for a timer. This method takes a "minute" parameter, which is what time the timer should trigger a tick. For example, if the "minute" parameter is 20, then the timer will tick at the intervals in the timetable above.
int CalculateTimerInterval(int minute)
{
if (minute <= 0)
minute = 60;
DateTime now = DateTime.Now;
DateTime future = now.AddMinutes((minute - (now.Minute % minute))).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
TimeSpan interval = future - now;
return (int)interval.TotalMilliseconds;
}
This code is used as follows:
static System.Windows.Forms.Timer t;
const int CHECK_INTERVAL = 20;
static void Main()
{
t = new System.Windows.Forms.Timer();
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
t.Tick += new EventHandler(t_Tick);
t.Start();
}
static void t_Tick(object sender, EventArgs e)
{
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
}
System.Timers.Timer. If you want to run at specific times of the day, you will need to figure out how long it is until the next time and set that as your interval.
This is just the basic idea. Depending on how precise you need to be you can do more.
int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60 * 1000;
You may find help from Quartz.net http://quartznet.sourceforge.net/
Here is an example of a lightweight system using thread timing and an asynch call.
I know there are some downsides, but I like using this instead of a timer when kicking off a long running process (like schedualed backend services). Since it runs inline in the timer thread, you don't have to worry about it getting kicked off again before the the original call has finished. This could be extended quite a bit to make it use an array of datetimes as the trigger times or add some more abilities to it. I am sure some of you guys out there know some better ways.
public Form1()
{
InitializeComponent();
//some fake data, obviously you would have your own.
DateTime someStart = DateTime.Now.AddMinutes(1);
TimeSpan someInterval = TimeSpan.FromMinutes(2);
//sample call
StartTimer(someStart,someInterval,doSomething);
}
//just a fake function to call
private bool doSomething()
{
DialogResult keepGoing = MessageBox.Show("Hey, I did something! Keep Going?","Something!",MessageBoxButtons.YesNo);
return (keepGoing == DialogResult.Yes);
}
//The following is the actual guts.. and can be transplanted to an actual class.
private delegate void voidFunc<P1,P2,P3>(P1 p1,P2 p2,P3 p3);
public void StartTimer(DateTime startTime, TimeSpan interval, Func<bool> action)
{
voidFunc<DateTime,TimeSpan,Func<bool>> Timer = TimedThread;
Timer.BeginInvoke(startTime,interval,action,null,null);
}
private void TimedThread(DateTime startTime, TimeSpan interval, Func<bool> action)
{
bool keepRunning = true;
DateTime NextExecute = startTime;
while(keepRunning)
{
if (DateTime.Now > NextExecute)
{
keepRunning = action.Invoke();
NextExecute = NextExecute.Add(interval);
}
//could parameterize resolution.
Thread.Sleep(1000);
}
}
Another strategy for this would be to record the LAST TIME that the process was run and determine if your desired interval has elapsed since that time. In this strategy, you would code your event to fire if the elapsed time is equal to OR GREATER THAN the desired interval. In this way you can handle instances where long intervals (once per day, for example) could be missed if the computer were to be down for some reason.
So for example:
lastRunDateTime = 5/2/2009 at 8pm
I want to run my process every 24 hours
On a timer event, check whether 24 hours OR MORE passed since the last time the process was run.
If yes, run the process, update lastRunDateTime by adding the desired interval to it (24 hours in this case, but whatever you need it to be)
Obviously, for this to recover after the system has gone down, you will need to store lastRunDateTime in a file or database somewhere so the program could pick up where it left off on recovery.
System.Windows.Forms.Timer (or System.Timers.Timer)
but since now you say you don't want to use Timers, you can run a lightweight wait process on another thread (check time, sleep a few seconds, check time again...) or make a component that raises an event (using a lightweight wait process) on certain scheduled times or intervals
The following should do the trick.
static void Main(string[] Args)
{
try
{
MainAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static async Task MainAsync()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
// Start the timed event here
StartAsync(tokenSource.Token);
Console.ReadKey();
tokenSource.Cancel();
tokenSource.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
var nextRunTime = new DateTime();
switch (DateTime.Now.AddSeconds(1) < DateTime.Today.AddHours(12)) // add a second to current time to account for time needed to setup the task.
{
case true:
nextRunTime = DateTime.Today.AddHours(12); // Run at midday today.
break;
case false:
nextRunTime = DateTime.Today.AddDays(1).AddHours(12); // Run at midday tomorrow.
break;
}
var firstInterval = nextRunTime.Subtract(DateTime.Now);
Action action = () =>
{
// Run the task at the first interval, then run the task again at midday every day.
_timer = new Timer(
EventMethod,
null,
firstInterval,
DateTime.Today.AddDays(1).AddHours(12).Subtract(DateTime.Now)
);
};
// no need to await this call here because this task is scheduled to run later.
Task.Run(action);
return Task.CompletedTask;
}
private async void EventMethod(object state)
{
// do work
}
My goal is to run an import around 03:00 every night.
Here's my approach, using System.Timers.Timer:
private Timer _timer;
private Int32 _hours = 0;
private Int32 _runAt = 3;
protected override void OnStart(string[] args)
{
_hours = (24 - (DateTime.Now.Hour + 1)) + _runAt;
_timer = new Timer();
_timer.Interval = _hours * 60 * 60 * 1000;
_timer.Elapsed += new ElapsedEventHandler(Tick);
_timer.Start();
}
void Tick(object sender, ElapsedEventArgs e)
{
if (_hours != 24)
{
_hours = 24;
_timer.Interval = _hours * 60 * 60 * 1000;
}
RunImport();
}