C# Threading.Timer 为啥过了一会儿自己停了

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# Threading.Timer 为啥过了一会儿自己停了相关的知识,希望对你有一定的参考价值。

最近咱一研究Threading.Timer,因为之前的Forms.Timer 总是会出现卡屏的现象,所以觉得改用Threading.timer,可是我从网上找了一下方法,但是出现了一些问题,想向大家请教,我的源代码如下:
private void button1_Click(object sender, EventArgs e)

System.Threading.Timer timer1 = new System.Threading.Timer(new TimerCallback(timer_Elapsed), null, 0, 1000);


Thread t; //定义线程变量
private delegate void setText();//定义一个线程委托
//自定义方法Threadp,主要用于线程的调用
public void Threadp()

setText d = new setText(Threading); //实例化一个委托
this.Invoke(d); //在拥用此控件的基础窗体句柄的线程上执行指定的委托

//自定义方法Threading,主要作于委托的调用
public void Threading()

textBox1.Text = DateTime.Now.ToString();
t.Abort();//关闭线程


void timer_Elapsed(object sender)

t = new Thread(new ThreadStart(Threadp)); //实例化线程
t.Start();//启动线程

程序的功能即在TextBox中显示当前时间,一秒一刷新,但是呢,我测试了一些,发现Timer启动后,64s后就自己停止了,不知道为什么,各位大侠给瞧瞧。

上午回答过你的一个问题,这次看到你还在研究多线程的问题,就这个问题说一下我的理解吧。
可能原因:自定义方法Threading()是在UI主线程空闲上执行,而timer_Elapsed是在线程池当中的一个线程上执行,两者执行时很可能在执行 void timer_Elapsed(object sender)

t = new Thread(new ThreadStart(Threadp)); //实例化线程
//线程池当中的线程执行到这里时,UI主线程执行Threading()的 t.Abort();//关闭线程
t.Start();//启动线程


解决:你将所有方法都用try括起来,捕获异常信息,你一看就明白了。

建议:你一秒刷新一次TextBox控件,为什么要建一个Thread呢?(这个是很消耗资源的),可以像下面这样写。同时还可以查看this.Invoke和this.BeginInvoke的区别。这个还可以使用BackgroundWorker来完成这个功能。
private void button1_Click(object sender, EventArgs e)

System.Threading.Timer timer1 = new System.Threading.Timer(new TimerCallback(timer_Elapsed), null, 0, 1000);

//将这个d定义字段,放到方法外部
setText d = new setText(Threading); //实例化一个委托



void timer_Elapsed(object sender)


this.Invoke(d); //在拥用此控件的基础窗体句柄的线程上执行指定的委托
追问

这只是一个测试软件,实际上我是要在Timer中执行数据库操作及IO操作,很多东西,所以实际当中,开启一个线程还会消耗资源吗?我的目的就是为了解决,Forms.Timer卡的问题,现在Threading.Timer,我发现出现执行60次左右就停止的现象,所以就有点不知道怎么回事了。我试试你的方法吧,感谢你。

参考技术A private void button1_Click(object sender, EventArgs e)

System.Threading.Timer timer1 = new System.Threading.Timer(new TimerCallback(timer_Elapsed), null, 0, 1000);
GC.KeepAlive(timer1);

timer被回收了,加这个GC.KeepAlive(timer1);我以前也遇到过,这么解决的追问

非常感谢,我最后还是通过System.Threading.Timer timer1(全局),然后timer1 = new System.Threading.Timer(new TimerCallback(timer_Elapsed), null, 0, 1000);

这样就不会回收了。

C# 中的 System.Threading.Timer 似乎无法正常工作。它每 3 秒运行一次非常快

【中文标题】C# 中的 System.Threading.Timer 似乎无法正常工作。它每 3 秒运行一次非常快【英文标题】:System.Threading.Timer in C# it seems to be not working. It runs very fast every 3 second 【发布时间】:2012-09-29 13:19:23 【问题描述】:

我有一个计时器对象。我希望它每分钟运行一次。具体来说,它应该运行OnCallBack 方法并在OnCallBack 方法运行时变为非活动状态。一旦OnCallBack 方法完成,它(OnCallBack)就会重新启动计时器。

这是我现在拥有的:

private static Timer timer;

private static void Main()

    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();


private static void OnCallBack()

    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer

但是,它似乎不起作用。它每 3 秒运行一次非常快。即使如果提高一个周期(1000 * 10)。好像对1000 * 10视而不见@

我做错了什么?

【问题讨论】:

来自Timer.Change:“如果dueTime为零(0),则立即调用回调方法。”。对我来说似乎是零。 是的,但那又怎样?也有一个时期。 如果也有月经怎么办?引用的句子没有对期间价值提出任何要求。它只是说“如果这个值为零,我将立即调用回调”。 有趣的是,如果你将dueTime和period都设置为0,计时器将每秒运行一次并立即启动。 【参考方案1】:

这不是 System.Threading.Timer 的正确用法。当您实例化 Timer 时,您几乎总是应该执行以下操作:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

这将指示计时器在间隔过去后仅计时一次。然后在您的回调函数中,您在工作完成后更改计时器,而不是之前。示例:

private void Callback( Object state )

    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

因此不需要锁定机制,因为没有并发性。计时器将在下一个间隔过去 + 长时间运行操作的时间后触发下一个回调。

如果你需要精确地运行你的计时器 N 毫秒,那么我建议你使用 Stopwatch 测量长时间运行操作的时间,然后适当地调用 Change 方法:

private void Callback( Object state )

   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );


强烈鼓励任何正在使用 .NET 并且正在使用 CLR 的人但没有阅读过 Jeffrey Richter 的书 - CLR via C#,请尽快阅读。定时器和线程池在那里有非常详细的解释。

【讨论】:

我不同意private void Callback( Object state ) // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); Callback 可能会在操作完成之前再次调用。 我的意思是Long running operation 可能比TIME_INTERVAL_IN_MILLISECONDS 花费更多的时间。那会发生什么? 回调不会再被调用,这就是重点。这就是我们将 Timeout.Infinite 作为第二个参数传递的原因。这基本上意味着不要再为计时器打勾。然后在我们完成操作后重新安排打勾。 这里是线程的新手——如果你传入计时器,你认为这可能与ThreadPool 相关吗?我正在考虑这样一种情况,即在某个时间间隔产生一个新线程来执行一项工作 - 然后在完成时降级到线程池。 System.Threading.Timer 是一个线程池计时器,它在线程池上执行它的回调,而不是专用线程。定时器完成回调例程后,执行回调的线程回到池中。【参考方案2】:

定时器不需要停止,see nice solution from this post:

"您可以让计时器继续触发回调方法,但将不可重入代码包装在 Monitor.TryEnter/Exit 中。在这种情况下无需停止/重新启动计时器;重叠调用不会获取锁并返回马上。”

private void CreatorLoop(object state) 
 
   if (Monitor.TryEnter(lockObject))
   
     try
     
       // Work here
     
     finally
     
       Monitor.Exit(lockObject);
     
   
 

【讨论】:

这不适合我的情况。我需要准确地停止计时器。 您是否试图阻止多次输入回调?如果没有,你想达到什么目的? 1.防止多次进入回调。 2.防止执行次数过多。 这正是它的作用。 #2 只要在 if 语句 if 对象被锁定后立即返回,开销并不大,特别是如果你有这么大的间隔。 这并不能保证在最后一次执行后不少于 调用代码(可以在前一个滴答释放锁后一微秒触发一个新的计时器滴答)。这取决于这是否是一个严格的要求(从问题的描述中并不完全清楚)。【参考方案3】:

是否必须使用System.Threading.Timer

如果没有,System.Timers.Timer 有方便的 Start()Stop() 方法(还有一个 AutoReset 属性可以设置为 false,这样就不需要 Stop(),您只需在执行后调用 Start() )。

【讨论】:

是的,但这可能是一个真正的要求,或者只是碰巧选择了计时器,因为它是最常用的。遗憾的是,.NET 有大量的计时器对象,重叠了 90%,但仍然(有时微妙地)不同。当然,如果是需求的话,这个方案根本不适用。 根据documentation:Systems.Timer 类仅在 .NET Framework 中可用。它不包含在 .NET 标准库中,并且在其他平台上不可用,例如 .NET Core 或通用 Windows 平台。在这些平台上,以及跨所有 .NET 平台的可移植性,您应该改用 System.Threading.Timer 类。【参考方案4】:

我会这样做:

private static Timer timer;
 private static void Main()
 
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 

  private static void OnCallBack()
  
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  

并忽略 period 参数,因为您正在尝试自己控制周期。


您的原始代码正在尽可能快地运行,因为您一直为 dueTime 参数指定 0。来自Timer.Change

如果dueTime为零(0),则立即调用回调方法。

【讨论】:

是否需要配置定时器?为什么不用Change() 方法? 每次都设置计时器是绝对没有必要和错误的。 @IvanZlatanov 您能否就此提供更多见解。当我们调用 dispose 时,对象将从内存中清除,这很好,对吧?我所看到的只是一些可能影响解决方案的额外过程。只是大声思考...... @SudhakarChavali 您应该始终在使用完对象后将其丢弃。在这个例子中,因为定时器有 Change 方法可以启用/禁用实例,所以不需要释放它并创建一个新的。【参考方案5】:
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  , source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>...), source.Token)

如果你喜欢在里面使用一些循环线程...

public async void RunForestRun(CancellationToken token)

  var t = await Task.Factory.StartNew(async delegate
   
       while (true)
       
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task =>  Console.WriteLine("End delay"); );
           this.PrintConsole(1);
        
    , token) // drop thread options to default values;


// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.

【讨论】:

以上是关于C# Threading.Timer 为啥过了一会儿自己停了的主要内容,如果未能解决你的问题,请参考以下文章

为啥 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?

System.Threading.Timer:它为啥讨厌我?

如何在 C# 中创建计时器而不使用 System.Timers.Timer 或 System.Threading.Timer

Threading.Timer 应用程序消耗超过 50% 的 CPU,为啥?

C# System.Threading.Timer如何停止

捕获倒计时或滴答事件 - Threading.Timer C#