Timer(回调,...)回调函数是否会添加到堆栈中?垃圾收集Q与此

时间:2013-02-19 20:40:09

标签: c# timer garbage-collection callstack

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

防止垃圾被收集?

3 个答案:

答案 0 :(得分:1)

编辑x1 - 意思是“堆”,而不是第二句中的“堆叠”...(d'哦!)

我认为这不会导致堆栈溢出的原因非常简单。行var t = new Timer( ...在堆上创建一个新对象。函数指针保留在对象内部,理论上应该永远不会被添加到堆栈中,直到实际调用它为止。当调用attemptArchivalProcess()时,它会轮流调用initializeTimer()(添加到堆栈),但这会完成并在同一个线程上正常退出(从堆栈中删除)。当Timer启动时,它将从堆栈的2个调用条目开始。

现在,所有这些都说,我知道堆栈的复杂程度越来越高,但我的观点是最终你有2个方法被调用然后正确退出 - 并且当它们退出时应该正确清理

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

答案 1 :(得分:1)

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

定时器

回答你的第一个问题:正如你已经总结的那样,计时器会在它的代表身上消失。委托将在一个单独的线程上执行,每个计时器过去将获得一个全新的自己的堆栈来执行。因此,无限计时器过去的事件永远不会触发StackOverflowException

等到永远?

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

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

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

到Console.ReadLine()

如果您有控制台应用程序,则只需等待用户输入即可。主线程将永远等待而不消耗处理器能力。

根据Servy的建议,创建一个计划任务

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

Windows服务

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

循环时永不结束的替代方案 - WaitHandle

您还可以使用信号机制(例如使用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个实例来访问这个对象。