C# 同步计时器以防止重叠

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/684200/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 13:20:07  来源:igfitidea点击:

Synchronizing a timer to prevent overlap

c#multithreadingtimeroverlap

提问by JoshRivers

I'm writing a Windows service that runs a variable length activity at intervals (a database scan and update). I need this task to run frequently, but the code to handle isn't safe to run multiple times concurrently.

我正在编写一个 Windows 服务,它每隔一段时间运行一个可变长度的活动(数据库扫描和更新)。我需要经常运行此任务,但要处理的代码并发运行多次并不安全。

How can I most simply set up a timer to run the task every 30 seconds while never overlapping executions? (I'm assuming System.Threading.Timeris the correct timer for this job, but could be mistaken).

我怎样才能最简单地设置一个计时器来每 30 秒运行一次任务,而不会重叠执行?(我假设System.Threading.Timer是这项工作的正确计时器,但可能是错误的)。

采纳答案by Reed Copsey

You could do it with a Timer, but you would need to have some form of locking on your database scan and update. A simple lockto synchronize may be enough to prevent multiple runs from occurring.

您可以使用 Timer 来做到这一点,但您需要对数据库扫描和更新进行某种形式的锁定。简单lock的同步可能足以防止发生多次运行。

That being said, it might be better to start a timer AFTER you're operation is complete, and just use it one time, then stop it. Restart it after your next operation. This would give you 30 seconds (or N seconds) between events, with no chance of overlaps, and no locking.

话虽如此,最好在操作完成后启动计时器,只使用一次,然后停止它。下次操作后重新启动它。这将使您在事件之间有 30 秒(或 N 秒)的时间,没有重叠的机会,也没有锁定。

Example :

例子 :

System.Threading.Timer timer = null;

timer = new System.Threading.Timer((g) =>
  {
      Console.WriteLine(1); //do whatever

      timer.Change(5000, Timeout.Infinite);
  }, null, 0, Timeout.Infinite);

Work immediately .....Finish...wait 5 sec....Work immediately .....Finish...wait 5 sec....

立即工作.....完成...等待 5 秒....立即工作.....完成...等待 5 秒....

回答by jsw

I'd use Monitor.TryEnter in your elapsed code:

我会在您的已用代码中使用 Monitor.TryEnter:

if (Monitor.TryEnter(lockobj))
{
  try
  {
    // we got the lock, do your work
  }
  finally
  {
     Monitor.Exit(lockobj);
  }
}
else
{
  // another elapsed has the lock
}

回答by Steven Evers

instead of locking (which could cause all of your timed scans to wait and eventually stack up). You could start the scan/update in a thread and then just do a check to see if the thread is still alive.

而不是锁定(这可能导致所有定时扫描等待并最终堆积)。您可以在线程中开始扫描/更新,然后检查该线程是否还活着。

Thread updateDBThread = new Thread(MyUpdateMethod);

...

...

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    if(!updateDBThread.IsAlive)
        updateDBThread.Start();
}

回答by Jim Mischel

I prefer System.Threading.Timerfor things like this, because I don't have to go through the event handling mechanism:

我更喜欢这样System.Threading.Timer的事情,因为我不必通过事件处理机制:

Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);

object updateLock = new object();
void UpdateCallback(object state)
{
    if (Monitor.TryEnter(updateLock))
    {
        try
        {
            // do stuff here
        }
        finally
        {
            Monitor.Exit(updateLock);
        }
    }
    else
    {
        // previous timer tick took too long.
        // so do nothing this time through.
    }
}

You can eliminate the need for the lock by making the timer a one-shot and re-starting it after every update:

您可以通过使计时器一次性并在每次更新后重新启动来消除对锁定的需要:

// Initialize timer as a one-shot
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);

void UpdateCallback(object state)
{
    // do stuff here
    // re-enable the timer
    UpdateTimer.Change(30000, Timeout.Infinite);
}

回答by grieve

You could use the AutoResetEvent as follows:

您可以按如下方式使用 AutoResetEvent:

// Somewhere else in the code
using System;
using System.Threading;

// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);

void MyWorkerThread()
{
   while(1)
   {
     // Wait for work method to signal.
        if(autoEvent.WaitOne(30000, false))
        {
            // Signalled time to quit
            return;
        }
        else
        {
            // grab a lock
            // do the work
            // Whatever...
        }
   }
}

A slightly "smarter" solution is as follow in pseudo-code:

一个稍微“聪明”的解决方案如下伪代码:

using System;
using System.Diagnostics;
using System.Threading;

// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);

void MyWorkerThread()
{
  Stopwatch stopWatch = new Stopwatch();
  TimeSpan Second30 = new TimeSpan(0,0,30);
  TimeSpan SecondsZero = new TimeSpan(0);
  TimeSpan waitTime = Second30 - SecondsZero;
  TimeSpan interval;

  while(1)
  {
    // Wait for work method to signal.
    if(autoEvent.WaitOne(waitTime, false))
    {
        // Signalled time to quit
        return;
    }
    else
    {
        stopWatch.Start();
        // grab a lock
        // do the work
        // Whatever...
        stopwatch.stop();
        interval = stopwatch.Elapsed;
        if (interval < Seconds30)
        {
           waitTime = Seconds30 - interval;
        }
        else
        {
           waitTime = SecondsZero;
        }
     }
   }
 }

Either of these has the advantage that you can shutdown the thread, just by signaling the event.

这两种方法都有一个优点,你可以关闭线程,只需通过事件信号。



Edit

编辑

I should add, that this code makes the assumption that you only have one of these MyWorkerThreads() running, otherwise they would run concurrently.

我应该补充一点,这段代码假设您只运行这些 MyWorkerThreads() 之一,否则它们将同时运行。

回答by sscheider

I've used a mutex when I've wanted single execution:

当我想要单次执行时,我使用了互斥锁:

    private void OnMsgTimer(object sender, ElapsedEventArgs args)
    {
        // mutex creates a single instance in this application
        bool wasMutexCreatedNew = false;
        using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
        {
            if (wasMutexCreatedNew)
            {
                try
                {
                      //<your code here>
                }
                finally
                {
                    onlyOne.ReleaseMutex();
                }
            }
        }

    }

Sorry I'm so late...You will need to provide the mutex name as part of the GetMutexName() method call.

抱歉我来晚了...您需要提供互斥锁名称作为 GetMutexName() 方法调用的一部分。