Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/279.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如果前一个线程仍然繁忙,如何让计时器跳过_C#_Multithreading_Windows Services_Timer - Fatal编程技术网

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

C# 如果前一个线程仍然繁忙,如何让计时器跳过,c#,multithreading,windows-services,timer,C#,Multithreading,Windows Services,Timer,我创建了一个windows服务,它应该每隔60秒检查数据库中的某个表中的新行。对于添加的每一行,我都需要在服务器上执行一些繁重的处理,有时可能需要60秒以上 我在我的服务中创建了一个计时器对象,它每60秒滴答一次并调用所需的方法。 因为我不希望这个计时器在处理找到的新行时滴答作响,所以我将这个方法包装在一个lock{}块中,这样其他线程就无法访问它 它看起来像这样: Timer serviceTimer = new Timer(); serviceTimer.Interval = 60; ser

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

我在我的服务中创建了一个计时器对象,它每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_Appeased方法是在第一次处理完成后立即关闭,还是等待计时器再次计时

我想做的是——如果处理需要超过60秒,计时器会注意到线程被锁定,然后再等待60秒再次检查,这样我就不会陷入线程队列等待前一个线程完成的情况

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


谢谢

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

// 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没有指定重入是仅由计时器引起的,还是服务是多线程的。假设是后者,但如果是前者,则如果计时器停止(自动重置或手动),则无需锁定。

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

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

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

后台工作人员可以很容易地为您提供选项检查当前仍在进行的处理,并一次处理1项。线程池将使您能够在每次勾选(如有必要)时继续将项目排队到后台线程

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

对于服务,我建议您考虑使用线程池方法。这样,您可以使用计时器每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
    }

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

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

将计时器自动重置设置为false。从最后开始。这是你可能感兴趣的完整答案

另一种可能性是这样的:

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}
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();

其他答案也有类似的变化,允许计时器一直滴答作响,只有在可以获得锁时才工作,而不是停止计时器

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

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

有一种很好的方法可以通过反应式扩展解决这个问题。这是代码,您可以在这里阅读更完整的解释:

你可以这样使用它:

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}
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();

与我的解决方案相比,我更喜欢nonnb的解决方案,但我将保留我的解决方案,以便在您无法阻止事件触发时提供一个示例。此外,允许事件继续触发在某些情况下也很有用。您可以记录它或在主进程之外执行一些其他处理。不要使用
lock(this)
-将autoreset设置为false会更容易,那么我们就不需要锁定任何东西这也是我使用的方法。已用事件处理程序刚好失效,计时器可以不使用。这是一个非常酷的解决方案,并展示了RX调度程序的一个有趣的用法,这可能不是很明显。这个解决方案对我来说效果很好。也让我对调度程序有了新的认识