Thread.Sleep或Thread.Timer会导致系统挂起?

5

我已经寻找了一个星期以上,但我没有找到任何人遇到类似于我所看到的问题。

我正在处理一个在Windows XP上运行且使用Visual Studio 2003开发的旧应用程序。大约三周前,应用程序突然变得无响应,操作员必须按下Windows三指敬礼(CTRL-ALT-DEL)以启动任务管理器,终止应用程序进程(显示为未响应),并重新启动应用程序。

我设法在调试器中让它发生一次,并且当我暂停应用程序时,它正在等待系统从尝试设置System.Windows.Forms.Timer.Interval属性返回。

这是对象及其出现停顿的地方,这不是源代码中的写法

internal static System.Windows.Forms.Timer timerMtimeOut;

// This is in an initialization method.
timerMtimeOut = new System.Windows.Forms.Timer();   
timerMtimeOut.Tick += new System.EventHandler(timerMtimeOut_Tick);

// this is how it's value is set.
timerMtimeOut.Interval = 1 * msec;  // <-- This is where it was in the debugger
timerMtimeOut.Enabled = true;


private static void timerMtimeOut_Tick(object sender, System.EventArgs e)
{           
    mTimeOut +=timerMtimeOut.Interval/msec;
}

该应用程序基本上变得无响应,必须使用任务管理器将其关闭并重新启动。

它多年来一直运行良好,但在大约2-3周前开始出现这种情况。

有其他人看到过这种行为吗?


大多数类似这样的问题都与编码有关,为什么不展示你怀疑导致程序卡住的代码呢? - T McKeown
创建了多少个线程?调用/创建计时器的次数是多少? - T McKeown
值得一提的是,我们有两台机器正在运行这个源代码,并且应用程序在第二台机器上没有变得无响应。 - fmarcelino
我想你已经意识到自己可能在追逐一只野鹅。仅仅因为你在自己的机器上看到了某些事情发生并且代码出现了问题,并不意味着其他机器上也是这样。无论如何,是否有可能完全禁用计时器并查看问题是否消失在另一台机器上,并确信它与此有关?你的评论表明你可能已经这样做了,但并不清楚。 - Steve
它已经运行了好几年,然后这种情况突然发生了。真的吗?在它开始出现问题之前没有进行任何代码更改吗?这让人难以置信。你确定代码中没有任何更改吗? - Jim Mischel
显示剩余2条评论
1个回答

1
最可能的原因是您正在以不适当的方式使用计时器。代码的第一行提供了一个提示:
internal static System.Windows.Forms.Timer timerMtimeOut;

没有充分的理由将System.Windows.Forms.Timer声明为静态,因为这个特定的计时器被设计为与特定的窗口(Form实例)相关联,并且将导致tick事件在该窗体的主线程中触发。它通过在计时器达到tick时向线程消息队列发送消息来实现。然后,在窗体线程(WndProc)中处理该消息以及所有其他消息,例如鼠标移动和键盘输入。
如果你实例化多个访问计时器的窗口,那么问题迟早会出现。
当操作系统表示进程无响应时,它通过查看主线程消息队列中的消息处理方式来检测。如果它们没有被处理,那么要么主线程正在运行漫长的例程,要么已经陷入了某些无限循环。从主线程调用Thread.Sleep()(如您问题标题中所述)将导致消息处理暂停(并防止在睡眠期间处理ticks)。
长时间的程序应该在单独的线程中运行,以保持应用程序的响应。您可能想要显示进度条,并给用户机会中止进程。如果您在主线程中运行冗长的进程,则很难做到这一点。有一个肮脏的解决方法,就是在循环中某处调用Application.DoEvents(),它将实时处理消息队列。但是,在使用此解决方法时,务必非常小心地禁用UI按钮,否则您的用户可能会多次启动(递归)进程或更改进程依赖的其他状态。
有点跑题了,但变量的名称表明您正在尝试计算冗长进程期间的间隔数?请注意,如果那个冗长的进程正在窗体的主线程中运行,计时器的滴答事件将在消息循环中排队,并且只有在冗长的进程完成其工作并恢复消息处理后才会执行。评论中提到的使用DateTime字段的方法比使用计时器更简单、更安全、更好。
当您需要一个在时钟滴答时立即执行代码的计时器时,您可以使用 System.Threading.Timer 而不是 System.Windows.Forms.Timer。如果您需要从多个线程控制它,请改用 System.Timers.Timer(该计时器是线程安全的)。请注意,这些计时器将在不同的线程中执行其事件。还要注意,您永远不应该从除表单主线程之外的其他线程操纵控件或表单。通过 Control.Invoke() 方法,框架内置了一种轻松的方法来实现这一点,该方法将使用消息循环来排队操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接