我有一个功能需要每隔午夜存档90天的电子邮件。我创建了一个类来处理这个问题,这是一个例子:
public void processArchives()
{
initializeTimer();
}
private void initializeTimer()
{
var now = DateTime.Now;
var tomorrow = now.AddDays(1);
var durationUntilMidnight = tomorrow.Date - now;
var t = new Timer(o => { attemptArchivalProcess(); }, null, TimeSpan.Zero, durationUntilMidnight);
}
private void attemptArchivalProcess()
{
//perform archival
initializeTimer(); //re-start timer to process tomorrow
}
问题是,重复调用initializeTimer会导致堆栈溢出(重复函数调用)还是会“永远”运行良好?
我应该能够将processArchives()作为新线程调用,并保持线程打开,或者在init调用后需要某种循环:
while(!Program.Closing){ sleep(...); }
防止垃圾被收集?
答案 0 :(得分:1)
编辑x1 - 意思是“堆”,而不是第二句中的“堆叠”...(d'哦!)
我认为这不会导致堆栈溢出的原因非常简单。行var t = new Timer(
...在堆上创建一个新对象。函数指针保留在对象内部,理论上应该永远不会被添加到堆栈中,直到实际调用它为止。当调用attemptArchivalProcess()
时,它会轮流调用initializeTimer()
(添加到堆栈),但这会完成并在同一个线程上正常退出(从堆栈中删除)。当Timer
启动时,它将从堆栈的2个调用条目开始。
现在,所有这些都说,我知道堆栈的复杂程度越来越高,但我的观点是最终你有2个方法被调用然后正确退出 - 并且当它们退出时应该正确清理
或者至少这是我的推理。我完全承认我愿意对此进行更正......
答案 1 :(得分:1)
我认为你接近你的潜在解决方案。
回答你的第一个问题:正如你已经总结的那样,计时器会在它的代表身上消失。委托将在一个单独的线程上执行,每个计时器过去将获得一个全新的自己的堆栈来执行。因此,无限计时器过去的事件永远不会触发StackOverflowException
。
尝试回答您的第二个问题:您没有 编写无限循环来保持应用程序的活动状态。但是,你可以这样做,这完全取决于你的应用程序需要什么。权衡利弊。
幸运的是,有更多可行的解决方案(没有对错,权衡它们以满足您的需求)
您可以解决的解决方案的列表:
如果您有控制台应用程序,则只需等待用户输入即可。主线程将永远等待而不消耗处理器能力。
这样你就不必做任何事情来编写无限循环。您的应用程序将在完成后退出。如果您实际将此应用程序部署到用户,可能不是最帅的解决方案。
您还可以选择更成熟的解决方案并编写Windows服务(听起来比现在更复杂,编写基本的Windows服务非常简单)。这样你也不必费心去编写永无止境的循环,windows服务将永远按设计运行(单位你当然决定停止它)
您还可以使用信号机制(例如使用AutoResetEvent
),以便您的主线程可以等到设置了某个信号。这样你也不必主动等待(=不消耗处理器周期)。
你有很多选择,这一切都归结为你的具体需求,我无法为你决定。 你可以。 :)强>
所有这些话,让我们举一个例子。单元测试代表您的应用程序。计时器是一种不同的类型,即System.Timers.Timer
。您可以将该计时器设置为AutoReset
,这样您就不必创建新的计时器。
这里的例子,我希望你有意义(如果没有,评论,也许我可以澄清)
private Timer _processTimer;
private AutoResetEvent _resetSignal;
[Test]
public void YourImaginaryMainApp()
{
const int interval = 24 * 60 * 60 * 1000; // every day
_resetSignal = new AutoResetEvent(false);
_processTimer = new Timer(interval)
{
AutoReset = true
};
_processTimer.Elapsed += ProcessTimerOnElapsed;
_resetSignal.WaitOne( /*infinite*/);
}
答案 2 :(得分:0)
这是我试图解决这个问题....
System.Threading.Timer timerFunc = null;
public void processArchives()
{
initializeTimer();
while (!CSEmailQueues.StoppingService) //is
Thread.Sleep(CSEmailQueues.sleeptime);
timerFunc.Dispose();
return;
}
private void initializeTimer()
{
var now = DateTime.Now;
var tomorrow = now.AddDays(1);
var durationUntilMidnight = tomorrow.Date - now;
if (timerFunc != null) timerFunc.Dispose();
timerFunc = new System.Threading.Timer(o => { attemptArchivalProcess(); }, null, TimeSpan.Zero, durationUntilMidnight);
}
private void attemptArchivalProcess()
{
//Do Work
initializeTimer(); //re-start timer to process tomorrow
}
所以...这将处理计时器对象并在每次运行时创建一个新对象(因为此计时器仅执行一次)。除了是一个类级别的变量之外,总有一个对定时器的引用,所以垃圾收集器在我等待它触发时不会处理它。
然后我要做的就是创建一个从服务的onStart调用中调用processArchives()的线程,除非调用onStop并将StoppingService设置为true,否则这本质上应该永远运行。
另外我猜我不应该担心定时器回调跨线程使用timerFunc,因为在任何给定时间都不应该有超过1个实例来访问这个对象。