2

我有一个功能需要在每个午夜存档 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(...); }

防止它被垃圾收集?

4

3 回答 3

1

编辑 x1 - 在第二句中表示“堆”,而不是“堆栈”......(d'oh!)

我认为这不会导致堆栈溢出,原因很简单。行var t = new Timer(... 在堆上创建一个新对象。函数指针保存在对象内部,并且(理论上)在实际调用之前永远不要添加到堆栈中。当attemptArchivalProcess()被调用时,它会依次调用initializeTimer()(添加到堆栈中),但是这会在同一个线程上正常完成并退出(从堆栈中删除)。当Timer确实启动时,它将开始进入堆栈的 2-call 条目。

现在,这一切都说,我知道堆栈的幕后复杂性增加了,但我的观点是,最终你有两个方法被调用,然后正确退出 - 并且在它们退出时应该正确清理。

或者至少这是我的推理。我完全承认我愿意对此进行更正......

于 2013-02-19T20:45:57.373 回答
1

我认为您已接近潜在的解决方案。

定时器

回答您的第一个问题:正如您已经得出的结论,计时器将在其委托上经过。委托将在一个单独的线程上执行,并且每个计时器经过都会获得一个全新的自己的堆栈来执行。因此,无休止的计时器已过事件将永远不会触发StackOverflowException.

等到永远?

尝试回答您的第二个问题:您不必编写无限循环来保持您的应用程序存活但是,您可以这样做,这完全取决于您的应用程序需要什么。权衡利弊。

幸运的是有更多可能的解决方案(没有对错,权衡它们以满足您的需求)

您可以考虑的解决方案列表:

Console.ReadLine()

如果你有一个控制台应用程序,你可以简单地等待用户输入。主线程将永远等待而不消耗处理器能力。

按照Servy的建议,创建一个计划任务

这样你就不需要做任何事情来编写无限循环。您的应用程序将在完成后简单地退出。如果您实际将此应用程序部署给用户,可能不是最漂亮的解决方案。

视窗服务

您也可以选择更成熟的解决方案并编写一个 Windows 服务(听起来比它更复杂,编写一个基本的 Windows 服务非常简单)。这样您也不必费心编写永无止境的循环,Windows 服务将按设计永远运行(当然,您决定停止它的单元)

永无止境的while循环的替代方案-WaitHandle

您还可以使用信号机制(例如使用 a 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*/);
    }
于 2013-02-19T21:24:17.880 回答
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 个实例来访问此对象。

于 2013-02-19T21:32:59.093 回答