views:

1122

answers:

9

Hello everybody.

I want to call a specific function on my C# application at a specific time. At first i thought about using a Timer (System.Time.Timer), but that soon became impossible to use. Why?

Simple. The Timer Class requires a Interval in milliseconds, but considering that i might want the function to be executed, lets says in a week that would mean:

  • 7 Days = 168 hours;
  • 168 Hours = 10,080 minutes;
  • 10,080 Minutes = 6,048,000 seconds;
  • 6,048,000 Seconds = 6,048,000,000 milliseconds;
  • So the Interval would be 6,048,000,000;

Now lets remember that the Interval accepted data type is int, and as we know int range goes from -2,147,483,648 to 2,147,483,647.

That makes Timer useless in this case once we cannot set a Interval bigger that 2,147,483,647 milliseconds.


So i need a solution where i could specify when the function should be called. Something like this:

solution.ExecuteAt = "30-04-2010 15:10:00";
solution.Function = "functionName";
solution.Start();

So when the System Time would reach "30-04-2010 15:10:00" the function would be executed in the application.

How can this problem be solved?

Thanks just by taking the time to read my question. But if you could provide me with some help i would be most grateful.


Additional Info: What these functions will do?

  • Getting climate information and based on that info:
    • Starting / Shutting down other Applications (most of them Console Based);
    • Sending custom Commands to those Console Applications;
    • Power down, Rebooting, Sleep, Hibernate the computer;
    • And if possible schedule the BIOS to Power Up the Computer;

EDIT:

It would seem that the Interval accepted data type is double, however if you set a value bigger that an int to the Interval, and call Start() it throws a exception [0, Int32.MaxValue].

EDIT 2:

Jørn Schou-Rode suggested using Ncron to handle the scheduling tasks, and at first look this seems a good solution, but i would like to hear about some who as worked with it.

+9  A: 

I would recommend that you just write a program that deals with the business part of it and then execute that program when necessary by using Windows Task Scheduler.

klausbyskov
That would mean writing multiple programs, one for each task, guess i will have to do that if nothings else works. Thanks.
Fábio Antunes
@Fábio: Have you considered the option of "oursourcing" the scheduling part of you application to [Quartz.NET](http://quartznet.sourceforge.net/) or [NCron](http://code.google.com/p/ncron/)?
Jørn Schou-Rode
@Jørn Schou-Rode: I haven't heard of them. For what i see, they sure are the best choice. Did you have any experience working with them?
Fábio Antunes
@Fábio: I have never actually used Quartz.NET, but from studying it, I have the impression of a very solid tool, providing many cool features. NCron is extremely simple and works very well for me, but being the main contributor I might just be a tad biased :)
Jørn Schou-Rode
@Jørn Schou-Rode Haha :-D
klausbyskov
@Jørn Schou-Rode: Thats just one more good reason to use it. I did a bit of digging at Ncron docs and seems pretty simple, efficient and easy to use. Good job ;)
Fábio Antunes
@Jørn Schou-Rode: Post your answer about Ncron... ;)
Fábio Antunes
+11  A: 

Your "Start()" method should spawn a thread that wakes up at a defined interval, checks the time, and if you haven't reached the desired time, goes back to sleep.

Adrian
This solution gets my vote. Make your solution a windows service.
Segfault
Not a bad idea. Like in the function that should be called by the Timer, will check if its time to do its job, however the interval will require some tweaks to make sure that the Timer calls the function at the desired time. Thanks.
Fábio Antunes
+1. BeginShamelessPlug this is actually how we solved it in our Scheduler product (http://demicode.com/scheduler) EndOfShamelessPlug
Peter Lillevold
A: 

You can use the System.Threading.Timer class, which provides a constructor accepting an interval expressed as an Int64, which should be enough for your needs.

Now for the other stuff :

  • You can start/stop/configure program using the Process class (I don't really get what you call "custom commands")
  • You cannot restart or shut down or control the local BIOS using native .NET classes. Rebooting / restarting is possible through Interop (calling native Windows API from .NET), and scheduling the BIOS is just impossible. Or maybe with a special server motherboard ? I don't know..
Shtong
Some BIOS can power up you computer at a designated time, wasn't sure if i could tell the Bios that with C#, a Int64 can take values up to 9,223,372,036,854,775,807 right ? If thats the case i'm pretty sure that would be enough.
Fábio Antunes
+1  A: 

You could write some sort of wrapper class for a Timer which takes a DateTime instance. Then you perform the following steps:

  1. Determine the difference between DateTime.Now and the desired time.
  2. If the difference (in milliseconds) is larger than the maximum allowed value for the Timer.Interval property, set the Interval to the maximum allowed value (i.e. double.MaxValue or whatever) and start it.
  3. Now, when the timer elapses the first time, you simply go back to step 1.

At some time, the difference will be smaller than the maximum allowed value for the Interval property, and then you could fire an event in your wrapper which ultimately calls the desired method.

gehho
A: 

Use the System.Threading.Timer:

    var timer = new System.Threading.Timer(delegate { }, // Pass here a delegate to the method
        null,
        TimeSpan.FromDays(7), // Execute Method after 7 days.
        TimeSpan.Zero);
Islam Ibrahim
Tried with a TimeSpan of 2 seconds, and worked. Then i tried with a TimeSpan of 100 days, and he reported that the time limit should be less than 2^32-2 And 2^32-2 = 4294967294. Don't know if these are hours, seconds... But this way better than a System.Timers.Timer.
Fábio Antunes
A: 

The class System.Threading.Timer has the same limitation too (it would throw an ArgumentOutOfRangeException according to MSDN).

There seems to be no .Net Framework class natively adept to circumvent the Int32.MaxValue milliseconds upper bound.

public static class Scheduler
{
    private const long TimerGranularity = 100;

    static Scheduler()
     {
         ScheduleTimer = new Timer(Callback, null, Timeout.Infinite, Timeout.Infinite);
        Tasks = new SortedQueue<Task>();
     }

    private static void Callback(object state)
    {
        var first = Tasks.Peek();
        if(first.ExecuteAt<DateTime.Now)
        {
            Tasks.Dequeue();
            var executionThread = new Thread(() => first.Function());
            executionThread.Start();                
        }
    }

    private static Timer ScheduleTimer { get; set; }

    public static void Start()
    {
        ScheduleTimer.Change(0, TimerGranularity);
    }
    public static void Add(Task task)
    {
        Tasks.Enqueue(task);
    }

    public static SortedQueue<Task> Tasks { get; set; }
}

public class Task : IComparable<Task>
{
    public Func<Boolean> Function { get; set; }

    public DateTime ExecuteAt { get; set; }

    public int CompareTo(Task other)
    {
        return ExecuteAt.CompareTo(other.ExecuteAt);
    }
}

The solution I'd use is something similar to the above example: a class Scheduler that manages all the Tasks (in order to avoid having a timer for each task we are going to schedule).

Tasks are added to the a queue able to perform sorted insertion. Note that SortedQueue<T> is not a type of the .Net Framework but an hypothetical, easy-to-code collection capable of sorted insertion on a comparable type T.

The scheduler awakes every TimerGranularity milliseconds and checks for the first task whose `ExecuteAt' time has been surpassed; then executes it on a separate thread.

Additional effort could be done by creating a list of all surpassed tasks (instead of the first one only); but I left it out for the sake of clarity.

Erik Burigo
Yes, System.Threading.Timer does have a limitation too, however far greater than System.Timers.Timer, I like your idea a Task Secheduler managing all tasks, that at every specific Time in this case 100 milliseconds checks all tasks he as and runs those that should be run at that time. I most definitely have to try this, sounds a pretty good idea. Thanks.
Fábio Antunes
Hi Fàbio and thank you for your reply :)On the [MSDN article][1] relative to the `System.Threading.Timer`'s constructor the upper limit seems to be equal to `Int32.MaxValue` (like the one to the `System.Time.Timer`'s `Interval` property); but I actually haven't tried it first-hand. [1]: http://msdn.microsoft.com/en-us/library/ah1h85ch(v=VS.100).aspx
Erik Burigo
+4  A: 

One approach to task scheduling, simliar to that proposed by klausbyskov, is to built your scheduling service on top of an existing .NET scheduling framework/library. Compared to using the Windows Task Scheduler, this has the advantages of (a) allowing several jobs to be defined in the same project and (b) keeping jobs and scheduling logic "together" - i.e. not relying on server settings prone to get lost in system upgrades/replacements.

I know of two open-source projects that offer this kind of functionality:

  • "Quartz.NET is a full-featured, open source job scheduling system that can be used from smallest apps to large scale enterprise systems." I have never actually used this framework myself, but from studying the website, I have the impression of a very solid tool, providing many cool features. The fact that there [quartz-net] tag on Stackoverflow might also indicate that it is actually used in the wild.

  • "NCron is a light-weight library for building and deploying scheduled background jobs on the .NET server platform." It does not have half as many features as Quartz.NET, and it does not have any tag on Stackoverflow, but the author (yours truly) believes that its low-friction API makes it somewhat easier to get started with.

Building your scheduling service on top of NCron, you can schedule a CleanupJob for weekly execution using a single line of code:

service.Weekly().Run<CleanupJob>();

Ok, you will need around three lines of boiler plate code on top of that to actually turn your project into a Windows service, but it sounds more impressive when I claim that it can be done with one line of code ;)

Jørn Schou-Rode
Thanks. I only asked you to write your answer so i could mark it as the accepted answer. And once you also are a main contributor to Ncron, i think its good to say, that even before you wrote your answer i already had integrated Ncron into my C# App, and i must also say that so far its working very well. One last thing does Ncron as some functionality that enables me to perform a task at every Hour, Week, Day, ... but at a specific time? Thanks.
Fábio Antunes
@Fábio: Depends on what you mean by "specific time". If specific time of day/week/year then *yes*. See the [wiki on fluent scheduling](http://code.google.com/p/ncron/wiki/FluentScheduling) for examples. If specific as in only once, then you would have to do something creative :)
Jørn Schou-Rode
@Jørn Schou-Rode: What i mean is simple: I want something to be executed every day at 08:30 for example. Would this be possible?
Fábio Antunes
@Fábio: The link in my last comment should help you then. Hint: `30 8 * * *`
Jørn Schou-Rode
@Jørn Schou-Rode: Right.. using a CrontabExpression! At first glance i didn't realized how they could help me. My Greatest thanks, for helping me and by building such great tool.
Fábio Antunes
A: 

I am writing a simple application that is making use of nCron scheduling framework. I dont know what i am doing wrong. This is the exception when i am trying to start the service from Service Manager.

Everything is successfully compiled and installed.Here is the exception that i am getting in Eventlog on starting application.

Event Type: Error

Event Source: nCronTestApplication Event Category: None Event ID: 0 Date: 5/18/2010 Time: 8:45:45 PM User: N/A Computer: HAKSIT-02 Description: An unhandled exception has occured. System.IO.FileNotFoundException: Could not load file or assembly 'C5, Version=1.1.0.0, Culture=neutral, PublicKeyToken=06a1b38866503b69' or one of its dependencies. The system cannot find the file specified. File name: 'C5, Version=1.1.0.0, Culture=neutral, PublicKeyToken=06a1b38866503b69' at NCron.Service.SchedulingService..ctor() at NCron.Service.Bootstrap.RunService(Action`1 setupHandler, Boolean debug) WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure logging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog]. For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.**

Any help is highly appreciated.

Tabish
A: 

Hi

I found calculation wrong at first comment, Milliseconds for 7 days should be 604,800,000. Not 7 days = 6,048,000,000 Milliseconds. Because 1 day = 86,400,000 milliseconds

Jenny