C# - 如何暂停应用程序直到计时器完成?

Posted

技术标签:

【中文标题】C# - 如何暂停应用程序直到计时器完成?【英文标题】:C# - How to pause application until timer is finished? 【发布时间】:2016-11-10 06:00:25 【问题描述】:

我有一个需要等待特定时间的应用程序,但如果需要,我还需要能够取消当前操作。我有以下代码:

private void waitTimer(int days)

    TimeSpan waitTime = TimeSpan.FromDays(days);
    System.Timers.Timer timer = new System.Timers.Timer(waitTime.TotalMilliseconds);   // Wait for some number of milliseconds
    timer.Enabled = true;
    timer.Start();
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler

    while (!TimerSettings.TimerFinished && !quitToken.IsCancellationRequested);  // Loop forever untill timer is finished or operation is cancled. 

    timer.Elapsed -= new ElapsedEventHandler(OnTimedEvent); // Unsubscribe

    DoWork(); // Do work when timer finishes.......

下面是定时器完成事件的事件处理程序:

private void OnTimedEvent(object obj, ElapsedEventArgs e)

    TimerSettings.TimerFinished = true;

while 循环只会无限循环,直到计时器完成或发出取消请求。我想保留此功能,但我不想在等待计时器完成时永远循环。我的计时器可以设置为以多天的间隔运行,因此循环这么长时间没有意义。

还有其他方法吗?

我知道我能做到:

Thread.Sleep(runDuration.TotalMilliseconds);

但是,这会阻塞,我将无法提出取消请求。

编辑:因此,为了详细说明我需要在这里暂停什么/为什么需要对我的应用程序进行更详细的说明。基本上我想要一个定期执行“工作”的应用程序。因此,根据下面提供的答案之一,如果我做了这样的事情:

class Program

    // Do something in this method forever on a regular interval 
    //(could be every 5min or maybe every 5days, it's up to the user)
    static void Main(string[] args)
    
        while(true)
        
          if(args?.Length > 0)
              waitTimer(args[0]);
          else 
              wiatTimer(TimeSpan.FromDays(1).TotalSeconds); // Default to one day interval
                     
    

private void waitTimer(int numIntervals)

    this.ElapsedIntervals = 0;
    this.IntervalsRequired = numIntervals;
    this.timer = new System.Timers.Timer(1000);   // raise the elapsed event every second
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler
    //timer.Enabled = true; timer.Start() does this for you, don't do this
    timer.Start();
    //thats all here


 private void OnTimedEvent(object obj, ElapsedEventArgs e)
 
    this.ElapsedIntervals += 1;
    if(this.CancelRequested)
    
       this.ElapsedIntervals = 0;
       this.timer.Stop();
       return;
    
    if(this.ElapsedIntervals >= this.IntervalsRequired)
    
       this.ElapsedIntervals = 0;
       this.timer.Stop();
       DoWork();   // This is where my work gets done.......
      return;
    
  

然后我的服务/控制台应用程序将启动并进入一个无限循环,整天设置计时器。以前,我实际上是在以下位置停止执行任何其他代码:

while (!TimerSettings.TimerFinished && !quitToken.IsCancellationRequested);

这至少有效,但如前所述,可能是暂停线程的资源密集型方式。基本上我真正需要的是一种方法来阻止我的线程,直到计时器结束。

EDIT2:这是我的最终实现,使用等待句柄似乎对我有用...

class TimerClass

    /// <summary>
    /// Initialize new timer. To set timer duration,
    /// either set the "IntervalMinutes" app config 
    /// parameter, or pass in the duration timespan.
    /// </summary>
    /// <param name="time"></param>
    internal bool StartTimer(CancellationToken quitToken, TimeSpan? duration = null)
    
        TimeSpan runDuration = new TimeSpan();
        runDuration = duration == null ? GetTimerSpan() : default(TimeSpan);

        if (runDuration != default(TimeSpan))
        
            WaitTimer(runDuration); // Waits for the runduration to pass
        
        return true;
    

    /// <summary>
    /// Get duration to run the timer for.
    /// </summary>
    internal TimeSpan GetTimerSpan()
    
        TimerSettings.Mode = App.Settings.Mode;
        DateTime scheduledTime = new DateTime();

        switch (TimerSettings.Mode)
        
            case "Daily":
                scheduledTime = DateTime.ParseExact(App.Settings.ScheduledTime, "HH:mm:ss", CultureInfo.InvariantCulture);
                if (scheduledTime > DateTime.Now)
                    TimerSettings.TimerInterval = scheduledTime - DateTime.Now;
                else
                    TimerSettings.TimerInterval = (scheduledTime + TimeSpan.FromDays(1)) - DateTime.Now;
                break;
            case "Interval":
                double IntervalMin = double.TryParse(App.Settings.PollingIntervalMinutes, out IntervalMin) ? IntervalMin : 15.00;
                int IntervalSec = Convert.ToInt32(Math.Round(60 * IntervalMin));
                TimeSpan RunInterval = new TimeSpan(0, 0, IntervalSec);
                TimerSettings.TimerInterval = RunInterval;
                break;
            case "Manual":
                TimerSettings.TimerInterval = TimeSpan.FromMilliseconds(0);
                break;
            default:
                TimerSettings.TimerInterval = (DateTime.Today + TimeSpan.FromDays(1)) - DateTime.Now;
                break;
        
        return TimerSettings.TimerInterval;
    

    /// <summary>
    /// Event handler for each timer tick.
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="e"></param>
    private void OnTimedEvent(object obj, ElapsedEventArgs e)
    
        ElapsedIntervals += 1;
        if (CancelRequested.IsCancellationRequested) // If the application was cancled
        
            ElapsedIntervals = 0;
            timer.Stop();
            WaitHandle.Set();
            return;
        
        if (ElapsedIntervals >= IntervalsRequired) // If time is up
        
            ElapsedIntervals = 0;
            timer.Stop();
            WaitHandle.Set();
            return;
        
    

    /// <summary>
    /// Timer method to wait for a
    /// specified duration to pass. 
    /// </summary>
    /// <param name="span"></param>
    private void WaitTimer(TimeSpan span)
    
        WaitHandle = new AutoResetEvent(false);
        int tickDuration = 1000;  // Number of milliseconds for each tick
        IntervalsRequired = Convert.ToInt64(span.TotalMilliseconds / (tickDuration > 0 ? tickDuration : 0.01));
        timer = new System.Timers.Timer(tickDuration);          // Raise the elapsed event every tick
        timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler for when each tick is complete
        timer.Start();           // Start ticking
        WaitHandle.WaitOne();    // Halt the main thread untill span is reached
    


    // Timer parameters: 
    private static long ElapsedIntervals  get; set; 
    private static long IntervalsRequired  get; set; 
    private static System.Timers.Timer timer  get; set; 
    private static CancellationToken CancelRequested  get; set; 
    private static string Mode  get; set; 
    private static TimeSpan TimerInterval  get; set; 
    private static EventWaitHandle WaitHandle  get; set; 


internal static class TimerSettings

    internal static string Mode  get; set; 
    internal static TimeSpan TimerInterval  get; set; 

【问题讨论】:

你应该详细说明你想“暂停”什么以及为什么。 如一个答案中所述,繁忙的循环是糟糕。也许用Thread.Sleep() 减慢它会有所帮助,但只是一点点,而且没有必要。您可以正确毫无困难地做到这一点。请参阅标记的副本。您可以Wait() 如接受的答案所示,或者(甚至更好)使用async 方法和await Task 【参考方案1】:

您应该查看 Timer.Elapsed Event 文档。当 AutoReset 属性设置为 true(这是默认设置)时,每次经过间隔时都会重复引发此事件。我会自己计算已经过去的时间间隔,并将其与此事件处理程序中所需的时间间隔进行比较,以检查是否到了停止计时器的时间。在这种情况下,您还可以处理取消。如果您的计时器完成了所需的间隔数,您可以从该事件处理程序中调用您的 doWork 函数。

private void waitTimer(int numIntervals)

    this.ElapsedIntervals = 0;
    this.IntervalsRequired = numIntervals;
    this.timer = new System.Timers.Timer(1000);   // raise the elapsed event every second
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler
    //timer.Enabled = true; timer.Start() does this for you, don't do this
    timer.Start();
    //thats all here


private void OnTimedEvent(object obj, ElapsedEventArgs e)

    this.ElapsedIntervals += 1;
    if(this.CancelRequested)
    
        this.ElapsedIntervals = 0;
        this.timer.Stop();
        return;
    
    if(this.ElapsedIntervals >= this.IntervalsRequired)
    
       this.ElapsedIntervals = 0;
       this.timer.Stop();
       DoWork();
       return;
    

https://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed(v=vs.110).aspx

在我看来,关于“暂停”,想要暂停的原因有两个,我不确定你的原因是什么:

    您希望阻止应用程序“完成”执行并正常终止。 您想推迟执行其他代码,直到所需的间隔数结束

如果你的理由是#2,那么这个答案是完整的。

【讨论】:

这是一个 Windows 服务。我喜欢你的计时器实现,但我的应用程序没有暂停。 那么您需要“暂停”应用程序的原因是什么?将相关函数中的最后一行代码作为对 waitTimer 的调用,然后通过从 Elapsed 事件处理程序中调用适当的 DoWork 函数来完成剩余的工作不是很好吗? 我明白你的意思,但根据我的应用程序的流程,在 waitTimer 方法中的 timer.Start(); 行之后,应用程序将继续执行。所以说我有一个循环运行 10 次的进程。在循环一,我打电话给 waitTimer(10) 。我的预期行为是等待十秒钟然后 doWork()。实际发生的是 waitTimer() 连续调用 10 次,而 doWork() 在最初的 10 秒过去后一次发生 10 次!我希望 doWork 每 10 秒发生一次。 另一个例子是,为了举例,这是一个控制台应用程序。如果我的控制台应用程序只做一件事怎么办:即在 Main 方法中调用 waitTimer(10)。应用程序不会打开,调用 waitTimer() 然后立即关闭吗?它永远不会持续运行足够长的时间让 OnTimedEvent() 触发,除非我再次放入一些“忙等待”循环或 thread.sleep()。 我建议您使用普通的控制台应用程序并将其作为计划任务运行,而不是在服务中使用计时器。 weblogs.asp.net/jongalloway/428303【参考方案2】:

首先:“你绝对不(!)想要'忙-等待'任何事情!”(BAD Dog!NO Biscuit!) p>

咳咳……

此问题的一个实际解决方案是在信号量(或任何其他合适的互斥对象...)上执行定时等待,而不是使用实际的计时器。如果您需要在等待完成之前退出等待,只需选通它正在等待的对象即可。

您目前的“解决方案”的关键问题是它会将进程猛烈撞击到100% CPU 利用率, 完全是浪费。 永远不要那样做!

【讨论】:

“你目前的“解决方案”的关键问题是它会将进程猛烈投入 100% CPU”,是的,但是 Thread.Sleep(100) 或他的 while 循环中的适当值将解决这个问题(而不是而不是全速旋转)。另外,很高兴看到您的“实用解决方案”,因为尽管阅读了您的答案,但我不知道如何实现它,而是将 Sleep 添加到 OP 的代码中。 支持您的评论,因为在现实世界中,这很可能就足够了。 “不管怎样,'足够好和平工作,'”正如我已故的叔叔曾经说过的那样。在原本繁忙的等待循环中进行“100 毫秒的睡眠”确实可以回避问题,我完全赞成“任何可行的方法”。 我同意评论表明这个答案在没有代码示例的情况下不是很有用。坦率地说,这个问题已经被问过很多次了,所以我真的不认为我们在 Stack Overflow 上需要另一个答案。但如果你要提供一个,它应该是一个很好的。对SemaphoreSlimManualResetEvent 等进行定时等待并不是一个坏建议,但考虑到当前的 TPL 功能,这有点过时了。 跟 OP 说话就像跟狗说话?严重地?忽略忙碌等待有其实际用例这一事实,因为它具有最低的延迟。

以上是关于C# - 如何暂停应用程序直到计时器完成?的主要内容,如果未能解决你的问题,请参考以下文章

暂停程序,直到 powershell 脚本完成

C#中简单的问题,关于WPF程序的计时器控件。

Unity:如何暂停执行直到函数终止,以便不跳过任何帧

redux-thunk:暂停组件执行,直到 Action Creator 完成

暂停 segue 直到 IAP 交易完成

如何在信号驱动的恶魔中暂停主循环?