我正在写运行在间隔的可变长度活性(数据库扫描和更新)Windows服务。我需要这个任务频繁运行,但处理的代码是不是安全的同时运行多次。

我怎样才能最简单地设置一个计时器来运行任务每30秒时,切勿重复执行? (我假定System.Threading.Timer是该作业的正确定时,但也可以是错误)。

有帮助吗?

解决方案

您可以用一个定时器做到这一点,但你需要有某种形式的锁定在你的数据库的扫描和更新。一个简单的lock同步可以是足以防止多次运行的发生。

话虽这么说,这可能是更好的完成之后,你操作完成时启动一个定时器,并且只使用它一次,然后停止。你的下一个操作后,重新启动它。这将使您事件之间30秒(或N秒),没有机会重叠的,并且没有锁定。

示例:

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

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

其他提示

我会用Monitor.TryEnter在经过代码:

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

我喜欢这样的事情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.
    }
}

您可以通过使计时器消除对锁需要一杆并重新启动它的每一个更新后:

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

而不是锁定(这可能会导致所有的定时扫描的等待,最终叠起)。你可以在一个线程开始扫描/更新,然后就做了检查,看是否线程仍然活着。

Thread updateDBThread = new Thread(MyUpdateMethod);

...

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

您可以使用的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...
        }
   }
}

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

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

无论是哪种具有的优点是可以关闭的线程,只通过用信号通知的事件。


修改

我要补充,这使得代码假设你只有这些MyWorkerThreads的一个()的运行,否则他们会同时运行。

我使用一个互斥当我想单个执行:

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

    }

对不起,我迟到了这么久......你需要提供互斥名作为GetMutexName()方法调用的一部分。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top