我创建了一个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秒再次检查,这样我就永远不会陷入困境等待前一个完成的线程队列。
我如何完成这个结果?
这样做的最佳做法是什么?
谢谢!
答案 0 :(得分:21)
您可以尝试在处理期间禁用计时器,例如
// 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未指定重入是否仅由计时器或服务是多线程引起的。假设后者,但如果前者然后锁定应该是不必要的,如果计时器停止(AutoReset或手动)
答案 1 :(得分:20)
在这种情况下你不需要锁。在启动之前设置timer.AutoReset = false。 完成处理后,在处理程序中重新启动计时器。这将确保计时器在每个任务之后 60秒。
答案 2 :(得分:7)
快速检查它是否正在运行。如果它正在运行,它将跳过此事件并等待下一个事件触发。
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;
}
}
答案 3 :(得分:7)
其他答案的类似变化,允许计时器保持滴答作响,只有在可以获得锁定时才能完成工作,而不是停止计时器。
将它放在已用事件处理程序中:
if (Monitor.TryEnter(locker)
{
try
{
// Do your work here.
}
finally
{
Monitor.Exit(locker);
}
}
答案 4 :(得分:4)
我建议您在处理时不要让计时器打勾。
将计时器自动重置设置为false。并在最后开始。这是您可能感兴趣的完整答案 Needed: A Windows Service That Executes Jobs from a Job Queue in a DB; Wanted: Example Code
答案 5 :(得分:2)
其他选项可能是使用BackGroundWorker类或TheadPool.QueueUserWorkItem。
后台工作人员可以轻松地为您选择检查当前仍在进行的处理并一次处理1个项目。 ThreadPool将使您能够在每次滴答(如有必要)后继续将项目排队到后台线程。
根据您的描述,我假设您正在检查数据库中队列中的项目。在这种情况下,我会使用ThreadPool将工作推送到后台,而不是减慢/停止检查机制。
对于服务,我建议你看一下使用ThreadPool方法。这样,您可以使用计时器每60秒检查一次新项目,然后将它们排队,然后让.Net计算出为每个项目分配的数量,并继续将项目推入队列。
例如:如果你只是使用一个计时器,你有5个新行,总共需要65秒的处理时间。使用ThreadPool方法,这将在65秒内完成,有5个后台工作项。使用Timer方法,这将需要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
}
答案 6 :(得分:2)
使用Reactive Extensions解决这个问题的方法非常简洁。这是代码,你可以在这里阅读更全面的解释: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();
答案 7 :(得分:0)
另一种可能性是这样的:
void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (System.Threading.Monitor.IsLocked(yourLockingObject))
return;
else
lock (yourLockingObject)
// your logic
;
}