开发者

How to let Timer skip tick if the previous thread is still busy

I created a windows service, that is supposed to check a certain table in the db for new rows every 60 seconds. For every new row that was added, I need to do some heavy processing on the server that could sometimes take more than 60 seconds.

I created a Timer object in my service, that ticks every 60 seconds and invokes the wanted method.

Since I don't want this timer to tick while processing the new lines found, I wrapped the method in a lock { } block, so this won't be accessible by another thread.

It looks something like this :

Timer serviceTimer开发者_StackOverflow社区 = new Timer();
serviceTimer.Interval = 60;
serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
serviceTimer.Start();

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    lock (this)
    {
        // do some heavy processing...
    }
}

Now, I'm wondering -

If my timer ticks, and finds a lot of new rows on the db, and now the processing will take more than 60 seconds, the next tick won't do any processing till the previous one finished. This is the effect I want.

But now, will the serviceTimer_Elapsed method go off immediatly once the first processing was finished, or will it wait for the timer to tick again.

What I want to happen is - if the processing requires more than 60 seconds, than the timer will notice the thread is locked, and wait another 60 seconds to check again so I will never get stuck in a situation where there are a queue of threads waiting for the previous one to finish.

How can i accomplish this result ?

What is the best practice for doing this ?

Thanks!


You might try disabling the timer during processing, something like

// Just in case someone wants to inherit your class and lock it as well ...
private static object _padlock = new object();
try
{
  serviceTimer.Stop(); 

  lock (_padlock)
    { 
        // do some heavy processing... 
    } 
}
finally
{
  serviceTimer.Start(); 
}

Edit : OP didn't specify whether the reentrancy was caused only by the timer or whether the service was multi threaded. Have assumed the later, but if the former then locking should be unnecessary if the timer is stopped (AutoReset or manually)


You don't need the lock in this case. Set timer.AutoReset=false before starting it. Restart the timer in the handler after you are done with your processing. This will ensure that the timer fires 60 seconds after each task.


A similar variation on other answers, that allows the timer to keep ticking and only do the work when the lock can be obtained, instead of stopping the timer.

Put this in the elapsed event handler:

if (Monitor.TryEnter(locker)
{
    try
    {
        // Do your work here.
    }
    finally
    {
        Monitor.Exit(locker);
    }
}


Put a quick check it see if the service is running. if it is running it will skip this event and wait for the next one to fire.

Timer serviceTimer = new Timer();
serviceTimer.Interval = 60;
serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
serviceTimer.Start();
bool isRunning = false;
void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    lock (this)
    {
        if(isRunning)
            return;
        isRunning = true;
    }
    try
    {
    // do some heavy processing...
    }
    finally
    {
        isRunning = false;
    }
}


I recommend you don't let the timer tick at all while its processing.

Set the Timers AutoReset to false. And start it at the end. Here's a full answer you might be interested in Needed: A Windows Service That Executes Jobs from a Job Queue in a DB; Wanted: Example Code


Other options might be to use a BackGroundWorker class, or TheadPool.QueueUserWorkItem.

Background worker would easily give you the option check for current processing still occurring and process 1 item at a time. The ThreadPool will give you the ability to continue queueing items every tick (if necessary) to background threads.

From your description, I assume you are checking for items in a queue in a database. In this case, I would use the ThreadPool to push the work to the background, and not slow/stop your checking mechanism.

For a Service, I would really suggest you look at using the ThreadPool approach. This way, you can check for new items every 60 seconds with your timer, then Queue them up, and let .Net figure out how much to allocate to each item, and just keep pushing the items into the queue.

For Example: If you just use a timer and you have 5 new rows, which require 65 seconds of processing time total. Using the ThreadPool approach, this would be done in 65 seconds, with 5 background work items. Using the Timer approach, this will take 4+ minutes (the minute you will wait between each row), plus this may cause a back-log of other work that is queueing up.

Here is an example of how this should be done:

Timer serviceTimer = new Timer();
    void startTimer()
    {
        serviceTimer.Interval = 60;
        serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
        serviceTimer.AutoReset = false;
        serviceTimer.Start();
    }
    void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            // Get your rows of queued work requests

            // Now Push Each Row to Background Thread Processing
            foreach (Row aRow in RowsOfRequests)
            {
                ThreadPool.QueueUserWorkItem(
                    new WaitCallback(longWorkingCode), 
                    aRow);
            }
        }
        finally
        {
            // Wait Another 60 Seconds and check again
            serviceTimer.Stop();
        }
    }

    void longWorkingCode(object workObject)
    {
        Row workRow = workObject as Row;
        if (workRow == null)
            return;

        // Do your Long work here on workRow
    }


There's quite a neat way of solving this with Reactive Extensions. Here's the code, and you can read a fuller explanation here: http://www.zerobugbuild.com/?p=259

public static IDisposable ScheduleRecurringAction(
    this IScheduler scheduler,
    TimeSpan interval,
    Action action)
{
    return scheduler.Schedule(
        interval, scheduleNext =>
    {
        action();
        scheduleNext(interval);
    });
}

And you could use it like this:

TimeSpan interval = TimeSpan.FromSeconds(5);
Action work = () => Console.WriteLine("Doing some work...");

var schedule = Scheduler.Default.ScheduleRecurringAction(interval, work);          

Console.WriteLine("Press return to stop.");
Console.ReadLine();
schedule.Dispose();


another posibility would be something like this:

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜