views:

3344

answers:

3

Which is the best timer approach for a C# console batch application that has to process as follows:

  1. Connect to datasources
  2. process batch until timeout occurs or processing complete. "Do something with datasources"
  3. stop console app gracefully.

related question: http://stackoverflow.com/questions/186084/how-do-you-add-a-timer-to-a-c-console-application

+1  A: 

When you say "until timeout occurs" do you mean "keep processing for an hour and then stop"? If so, I'd probably just make it very explicit - work out at the start when you want to finish, then in your processing loop, do a check for whether you've reached that time or not. It's incredibly simple, easy to test etc. In terms of testability, you may want a fake clock which would let you programmatically set the time.

EDIT: Here's some pseudocode to try to clarify:

List<DataSource> dataSources = ConnectToDataSources();
TimeSpan timeout = GetTimeoutFromConfiguration(); // Or have it passed in!
DateTime endTime = DateTime.UtcNow + timeout;

bool finished = false;
while (DateTime.UtcNow < endTime && !finished)
{
    // This method should do a small amount of work and then return
    // whether or not it's finished everything
    finished = ProcessDataSources(dataSources);
}

// Done - return up the stack and the console app will close.

That's just using the built-in clock rather than a clock interface which can be mocked, etc - but it probably makes the general appropriate simpler to understand.

Jon Skeet
It's me that's a bit confused with the approaches - I'm sure its simple. The timeout period will be read in from a parameter table which could be anything, as you "keep processing for an 'n period' and then stop"
mm2010
I must add that processing may be complete before timeout event occurs
mm2010
Looks workable to me - I will try that and advise.
mm2010
Will your routine actually timeout, ie not get stuck on: finished = ProcessDataSources(dataSources); ?
mm2010
The comment is important: ProcessDataSources needs to do a *small amount of work*. Basically you have to have a co-operative check to see whether or not the time is up. You could abort the thread from elsewhere, but that's just hideous and dangerous.
Jon Skeet
Looks like I will have no choice but to abort from elsewhere cause the timeout must stop the processing somehow - what to do I thinks by myself :)
mm2010
Let me describe the larger picture: It will be a scheduled job in sql server agent scheduler that will run the console application. The console application will read in paramaters and then run an external "MSDynamics AX" class. It has no control over the process except aborting the job it started.
mm2010
+2  A: 

It depends on how accurate do you want your stopping time to be. If your tasks in the batch are reasonably quick and you don't need to be very accurate, then I would try to make it single threaded:

DateTime runUntil = DataTime.Now.Add(timeout);
forech(Task task in tasks)
{
   if(DateTime.Now >= runUntil)
   {
        throw new MyException("Timeout");
   }
   Process(task);
}

Otherwise you need to go mulithreaded, which is always more difficult, because you need to figure out how to terminate your task in the middle without causing side effects. You could use the Timer from System.Timers: http://msdn.microsoft.com/en-us/library/system.timers.timer(VS.71).aspx or Thread.Sleep. When the time-out event occurs you can terminate the thread that does the actual processing, clean up and end the process.

Grzenio
Thanks, looks similar to previous answer - I will try it and advise
mm2010
Will your routine actually time-out and not get stuck on: "Process(task);" ?
mm2010
+1  A: 

Sorry for this being an entire console app... but here's a complete console app that will get you started. Again, I appologize for so much code, but everyone else seems to be giving a "oh, all you have to do is do it" answer :)

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
     static List<RunningProcess> runningProcesses = new List<RunningProcess>();

     static void Main(string[] args)
     {
      Console.WriteLine("Starting...");

      for (int i = 0; i < 100; i++)
      {
       DoSomethingOrTimeOut(30);
      }

      bool isSomethingRunning = false;

      do
      {
       foreach (RunningProcess proc in runningProcesses)
       {
        // If this process is running...
        if (proc.ProcessThread.ThreadState == ThreadState.Running)
        {
         isSomethingRunning = true;

         // see if it needs to timeout...
         if (DateTime.Now.Subtract(proc.StartTime).TotalSeconds > proc.TimeOutInSeconds)
         {
          proc.ProcessThread.Abort();
         }
        }
       }
      }
      while (isSomethingRunning);

      Console.WriteLine("Done!");    

      Console.ReadLine();
     }

     static void DoSomethingOrTimeOut(int timeout)
     {
      runningProcesses.Add(new RunningProcess
      {
       StartTime = DateTime.Now,
       TimeOutInSeconds = timeout,
       ProcessThread = new Thread(new ThreadStart(delegate
         {
          // do task here...
         })),
      });

      runningProcesses[runningProcesses.Count - 1].ProcessThread.Start();
     }
    }

    class RunningProcess
    {
     public int TimeOutInSeconds { get; set; }

     public DateTime StartTime { get; set; }

     public Thread ProcessThread { get; set; }
    }
}
Timothy Khouri
Thanks for this, in my case the more the better. :)
mm2010