如果前一个线程仍然繁忙,如何让计时器跳过刻度

发布于 2024-09-12 20:10:32 字数 858 浏览 2 评论 0原文

我创建了一个 Windows 服务,该服务应该每 60 秒检查数据库中的某个表是否有新行。对于添加的每个新行,我都需要在服务器上进行一些繁重的处理,有时可能需要 60 秒以上的时间。

我在服务中创建了一个 Timer 对象,该对象每 60 秒计时一次并调用所需的方法。
由于我不希望此计时器在处理找到的新行时计时,因此我将该方法包装在 lock { } 块中,因此其他线程无法访问该方法。

它看起来像这样:

Timer serviceTimer = 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...
    }
}

现在,我想知道 -
如果我的计时器滴答作响,并在数据库上发现很多新行,并且现在处理将花费超过 60 秒,则下一个滴答不会执行任何处理,直到前一个滴答完成。这就是我想要的效果。

但现在,serviceTimer_Elapsed 方法是在第一次处理完成后立即关闭,还是等待计时器再次计时。

我想要发生的是 - 如果处理需要超过 60 秒,那么计时器会注意到线程被锁定,并再等待 60 秒再次检查,这样我就永远不会陷入有线程队列的情况等待上一篇完成。

我怎样才能达到这个结果?
这样做的最佳实践是什么?

谢谢!

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 = 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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

一直在等你来 2024-09-19 20:10:32

您可能会尝试在处理过程中禁用计时器,例如

// 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(); 
}

编辑:OP没有指定重入是否仅由计时器引起,或者服务是否是多线程的。假设是后者,但如果是前者,那么如果计时器停止(自动重置或手动),则锁定应该是不必要的

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)

东风软 2024-09-19 20:10:32

在这种情况下你不需要锁。在启动之前设置timer.AutoReset=false。
处理完成后,重新启动处理程序中的计时器。这将确保计时器在每个任务后 60 秒触发。

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.

仅此而已 2024-09-19 20:10:32

其他答案的类似变体,允许计时器继续滴答作响,并且仅在获得锁定时才执行工作,而不是停止计时器。

将其放入已用事件处理程序中:

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

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);
    }
}
揽清风入怀 2024-09-19 20:10:32

快速检查一下服务是否正在运行。如果它正在运行,它将跳过此事件并等待下一个事件触发。

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;
    }
}

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;
    }
}
归途 2024-09-19 20:10:32

我建议您在处理时不要让计时器滴答作响。

将计时器自动重置设置为 false。并从最后开始。这是您可能感兴趣的完整答案
需要:从数据库中的作业队列执行作业的 Windows 服务;需要:示例代码

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

我的黑色迷你裙 2024-09-19 20:10:32

其他选项可能是使用 BackGroundWorker 类或 TheadPool.QueueUserWorkItem。

后台工作人员可以轻松地为您提供选项检查当前处理仍在发生并一次处理 1 项。 ThreadPool 将使您能够在每次更新(如果需要)时继续将项目排队到后台线程。

根据您的描述,我假设您正在检查数据库队列中的项目。在这种情况下,我将使用 ThreadPool 将工作推送到后台,而不是减慢/停止检查机制。

对于服务,我真的建议您考虑使用线程池方法。这样,您可以使用计时器每 60 秒检查一次新项目,然后将它们排队,让 .Net 计算出要分配给每个项目的量,然后继续将项目推入队列。

例如:如果您仅使用计时器并且有 5 个新行,则总共需要 65 秒的处理时间。使用线程池方法,这将在 65 秒内完成,并有 5 个后台工作项。使用计时器方法,这将需要 4 分钟以上(每行之间等待的分钟),而且这可能会导致其他正在排队的工作积压。

以下是应如何完成此操作的示例:

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
    }

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
    }
青巷忧颜 2024-09-19 20:10:32

有一种非常巧妙的方法可以通过响应式扩展来解决这个问题。这是代码,您可以在这里阅读更完整的解释: 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);
    });
}

你可以这样使用它:

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();

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();
雨落星ぅ辰 2024-09-19 20:10:32

另一种可能性是这样的:

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

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  
           ;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文