C# 计时器(回调,…)回调函数是否添加到堆栈中?垃圾收集

C# 计时器(回调,…)回调函数是否添加到堆栈中?垃圾收集,c#,timer,garbage-collection,callstack,C#,Timer,Garbage Collection,Callstack,我有一个功能,需要存档90天的电子邮件每午夜。我创建了一个类来处理这个问题,下面是一个示例: public void processArchives() { initializeTimer(); } private void initializeTimer() { var now = DateTime.Now; var tomorrow = now.AddDays(1); var durat

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

为了防止它被垃圾收集?

编辑x1-在第二句中的意思是“堆”,而不是“堆栈”。。。(哦!)

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

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


或者至少这是我的理由。我完全承认我对纠正这个问题持开放态度…

我认为你已经接近你的潜在解决方案了

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

等到永远? 尝试回答你的第二个问题:你不必写一个无休止的循环来保持你的应用程序的活力。但是,你可以这样做,这完全取决于你的应用程序需要什么。权衡利弊

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

您可以编辑的解决方案快照列表:

Console.ReadLine() 如果您有一个控制台应用程序,您可以简单地等待用户输入。主线程将永远等待,而不会消耗处理器电源

按照Servy的建议,创建一个计划任务 这样,您就不必做任何事情来编写无休止的循环。您的应用程序将在完成后退出。如果您实际将此应用程序部署给用户,可能不是最理想的解决方案

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

永无止境while循环的替代方法-WaitHandle 您还可以使用信号机制(例如,使用
自动resetEvent
),以便主线程可以等待,直到设置了某个信号。这样,您也不必主动等待(=不消耗处理器周期)

你有很多选择,这一切归结于你的具体需求,我不能为你决定。你可以。


所有这些话,让我们举个例子。单元测试代表您的应用程序。计时器是另一种类型,即
System.Timers.timer
。您可以将计时器设置为自动重置,这样就不必创建新的计时器

这里的例子,我希望它对你有意义(如果没有,评论,也许我可以澄清)


这是我试图解决的问题

    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个实例来访问此对象。

几乎可以肯定,您不应该有一个运行那么长时间的C#应用程序。让代码执行一次处理,然后使用Windows任务调度程序每晚午夜运行它。它将比您的代码更加健壮,能够处理诸如重新启动机器、更改系统时钟等事情。@Servy这只是一个服务模块,用于处理电子邮件排队系统的多个方面,该系统可以全天候运行。这不会改变我的评论。您应该安排作业,而不是全天候运行。@Servy并非我完全不同意,但“全天候运行的应用程序”不是非常接近windows服务吗?备份服务看起来与OP所要求的非常相似,似乎没有问题,或者我遗漏了什么?@bas我不是在提倡windows服务。这会好一点,但仍然不如使用任务调度器简单地调度任务那么有效。这意味着作业不是一直坐在那里无所事事,它简化了许多与时钟相关的问题,如夏令时、周期性时钟调整等。您确定
var t=new Timer(…)
在堆栈上创建了一个对象吗?此外,这对你的论点有影响吗?如果是
    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
    }