C# 带有自动恢复事件的Windows服务
我目前正在构建一个Windows服务,它需要处理数据库表中的消息队列。这个队列的长度可能不同,对数据库中的所有行执行可能需要5秒到55秒的时间(我目前使用的是500000条记录的测试数据集) Windows服务被配置为在30秒的计时器上运行,因此我尝试确保当计时器委托运行时,它无法再次运行,直到对该方法的上一个请求成功完成 我的Windows服务OnStart方法中有以下代码:C# 带有自动恢复事件的Windows服务,c#,multithreading,windows-services,C#,Multithreading,Windows Services,我目前正在构建一个Windows服务,它需要处理数据库表中的消息队列。这个队列的长度可能不同,对数据库中的所有行执行可能需要5秒到55秒的时间(我目前使用的是500000条记录的测试数据集) Windows服务被配置为在30秒的计时器上运行,因此我尝试确保当计时器委托运行时,它无法再次运行,直到对该方法的上一个请求成功完成 我的Windows服务OnStart方法中有以下代码: AutoResetEvent autoEvent = new AutoResetEvent(false);
AutoResetEvent autoEvent = new AutoResetEvent(false);
TimerCallback timerDelegate = new TimerCallback(MessageQueue.ProcessQueue);
Timer stateTimer = new Timer(timerDelegate, autoEvent, 1000, Settings.Default.TimerInterval); // TimerInterval is 30000
autoEvent.WaitOne();
以及MessageQueue.ProcessMessage中的以下代码:
Trace.Write("Starting ProcessQueue");
SmtpClient smtp = new SmtpClient("winprev-01");
AutoResetEvent autoEvent = (AutoResetEvent)stateObject;
foreach (MessageQueue message in AllUnprocessed)
{
switch (message.MessageType)
{
case MessageType.PlainText:
case MessageType.HTML:
SendEmail(smtp, message);
break;
case MessageType.SMS:
SendSms(message);
break;
default:
break;
}
}
autoEvent.Set();
Trace.Write("Ending ProcessQueue");
我正在使用DebugView分析服务运行时跟踪语句的视图,我可以看到“Starting ProcessQueue”的多个实例每30秒发生一次,这正是我试图避免发生的情况
总之:我想调用ProcessQueue并确保它不会再次执行,除非它完成了工作(这使我能够防止队列中的相同消息被多次处理)
我确信我在这里遗漏了一些非常明显的东西,因此,任何帮助都将不胜感激:)
Dave启动方法
AutoResetEvent autoEvent = new AutoResetEvent(true);
while (true)
{
autoEvent.WaitOne();
Thread t = new Thread(MessageQueue.ProcessMessage);
t.Start(autoEvent);
}
您的
ProcessMessage
从不检查是否发出了resetEvent
信号-它只是在运行
我在这里张贴如何解决这个问题。然而,这不是做你想做的事情的理想方法。请看我答案的底部
您对autoEvent.WaitOne()
的调用位于错误的位置;它应该位于ProcessMessage
方法的开头
AutoResetEvent autoEvent = (AutoResetEvent)stateObject;
autoEvent.WaitOne();
Trace.Write("Starting ProcessQueue");
SmtpClient smtp = new SmtpClient("winprev-01");
foreach (MessageQueue message in AllUnprocessed){
您还应该使用接受超时值(int或timespan)的重载,如果方法返回true
,则返回bool
,这意味着它已发出信号,因此您可以继续。如果它超时(因为另一个迭代仍在运行),您应该返回,而不是再次尝试运行代码
如果您不使用这样的重载,那么您所做的将无异于将ProcessMessage方法的代码包装在关键部分(例如,在全局变量上的lock()
),其他线程将被阻塞,然后不必要地运行
AutoResetEvent autoEvent = (AutoResetEvent)stateObject;
//wait just one ms to see if it gets signaled; returns false if not
if(autoEvent.WaitOne(1)){
Trace.Write("Starting ProcessQueue");
SmtpClient smtp = new SmtpClient("winprev-01");
foreach (MessageQueue message in AllUnprocessed){
请注意,实际上,*ResetEvent
在这里并不理想。实际上,您只需要检查实例是否已经在运行,如果已经在运行,则中止<代码>重置事件并不是为此而设计的。。。但我还是想解决使用ResetEvent的问题
可能更好的方法是在调用回调时简单地关闭计时器,然后在完成后重新启动计时器。这样,当代码仍在运行时,就不可能重新输入代码
不过,您绝对需要将回调方法中的所有代码包装在一个
try
/finally
中,以便始终在之后重新启动计时器。您需要的是一个同步计时器对象。在Win32中,这被称为可等待计时器(不幸的是,除非我弄错了,否则需要一些P/invoke)
以下是您将要做的:
- 创建可等待计时器(确保其自动重置)
- 将可等待计时器设置为30秒
- 循环:
- 具有无限超时的WaitForSingleObject(可等待计时器)
- 进程队列
如果处理时间超过30秒,则计时器将保持设置状态,直到您对其调用WaitForSingleObject。此外,例如,如果处理需要20秒,计时器将在10秒后发出信号。为什么不让您的学员禁用计时器,然后在完成工作后重新启用它(或者继续工作,如果计时器将立即过期)。如果计时器启动和代理唤醒之间的延迟小于30秒,则这应该是无懈可击的
while (true)
{
Trace.Write("Starting ProcessQueue")
stateTimer.Enabled = false;
DateTime start = DateTime.Now;
// do the work
// check if timer should be restarted, and for how long
TimeSpan workTime = DateTime.Now - start;
double seconds = workTime.TotalSeconds;
if (seconds > 30)
{
// do the work again
continue;
}
else
{
// Restart timer to pop at the appropriate time from now
stateTimer.Interval = 30 - seconds;
stateTimer.Enabled = true;
break;
}
}
您可以通过使用System.Threading.Timer轻松解决此问题。通过将其周期设置为零,使其成为一次性计时器。重新启动回调中的计时器。现在不可能重复执行回调 由于您频繁地运行它,因此另一种方法是使用线程。在OnStop()方法中,需要一个AutoResetEvent来通知线程停止。它的WaitOne()方法在使用接受毫秒计时参数的重载时为您提供一个空闲计时器
顺便说一句:注意OnStart()中的autoEvent.WaitOne()调用很麻烦。如果发送第一封电子邮件需要很长时间,服务控制器可能会超时。忽略它,您启动了计时器==服务启动。我认为您使这比需要的困难得多。为什么不创建一个单独的线程,围绕一个无限循环旋转,调用
MessageQueue.ProcessQueue
,然后等待一定时间再调用它呢。如果这一切都发生在一个线程上,那么任何事情都不可能并行发生
public class YourService : ServiceBase
{
private ManualResetEvent m_Stop = new ManualResetEvent(false);
protected override void OnStart(string[] args)
{
new Thread(Run).Start();
}
protected override void OnStop()
{
m_Stop.Set();
}
private void Run()
{
while (!m_Stop.WaitOne(TimeSpan.FromSeconds(30))
{
MessageQueue.ProcessMessage();
}
}
}
为什么不在MessageQueue.ProcessMessage方法的开头和结尾设置一个锁/触发器,然后在再次调用ProcessQueue之前检查它?@Bolu:这不是一个好主意。他会对代码进行迭代,基本上是排队、阻塞、等待第一个完成——然后他们可能会依次运行,然后什么也不做。你真正想要的是一个同步计时器对象——在Win32中,它被称为可等待计时器。@Andrew,当我说lock/trigger时,我指的是一个简单的布尔值。。。。启动ProcessQueue时将其设置为false,完成时将其设置为true,其他迭代只需检查它并中断(如果为false)实际上,如果他要在此处使用计时器,他应该做的是只触发一次,并仅在工作完成时重置计时器。。。根本不使用ResetEvent。这将不起作用;第二次和以后的迭代仍将运行,而不检查resetEvent是否发出了信号。这涉及的内容要多得多