如何使用Threading.Timer和Control.Invoke避免死锁
本文关键字:Invoke 死锁 Control 何使用 Threading Timer | 更新日期: 2023-09-27 17:49:25
WindowsCE 5.0,NET Compact framework 3.5
我需要定期更新UI,所以我决定使用Threading.Timer。我的代码如下所示,除了在Timer的回调过程中出现Presenter.Stop((之外,它运行得很好。
调试输出显示它从UpdateViewSafe较新退出,Stop将始终在Monitor处等待。Enter(sync(。我的错在哪里?我尝试使用Thread而不是Timer,但它在Thread的回调时也发生了死锁,所以我想问题出在锁定同步对象和Control.Invoke.之间
源代码:
class Presenter
{
private MyForm view;
private Timer timer;
private object sync;
public Presenter(MyForm form)
{
view = form;
sync = new object();
}
public void Start()
{
timer = new Timer(UpdateViewSafe, null, 0, 2000);
}
public void Stop()
{
System.Diagnostics.Debug.WriteLine("+++ Stop 1");
Monitor.Enter(sync);
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Dispose();
Monitor.Exit(sync);
System.Diagnostics.Debug.WriteLine("+++ Stop 2");
}
private void UpdateViewSafe(object state)
{
System.Diagnostics.Debug.WriteLine("+++ UpdateViewSafe 1");
Monitor.Enter(sync);
System.Diagnostics.Debug.WriteLine("+++ UpdateViewSafe 2");
Thread.Sleep(1000);
System.Diagnostics.Debug.WriteLine("+++ UpdateViewSafe 3");
view.InvokeIfNeeded(() => view.MyText = "text");
Monitor.Exit(sync);
System.Diagnostics.Debug.WriteLine("+++ UpdateViewSafe 4");
}
}
public static void InvokeIfNeeded(this Control control, Action doIt)
{
if (control == null) return;
if (control.InvokeRequired)
control.Invoke(doIt);
else
doIt();
}
调试输出:
+++ UpdateViewSafe 1
+++ UpdateViewSafe 2
+++ Stop 1
+++ UpdateViewSafe 3
我从来没有见过
+++ Stop 2
我需要定期更新UI,所以我决定使用Threading.Timer.
由于您正在使用Winforms,我建议您改用System.Windows.Forms.Timer
。它使用UI消息泵,因此代码在与UI相同的线程中执行,这意味着:
- 它是同步的
- 您不需要使用
Invoke
来更新UI控件
您不会有很好的精度(~50毫秒(,但在大多数情况下,它应该足以进行UI更新。
旁注:使用lock(lockObject){ ... }
而不是Monitor
(几乎相同,但更容易使用,因为关键部分的范围具体化了(
由于我们没有看到Start
和Stop
是如何被调用的,因此很难准确地判断您将如何获得所拥有的输出。
很可能在UpdateViewSafe
进入监视器之前调用并调度了Stop
,但它没有得到时间间隔。这允许UpdateViewSafe
执行到Sleep
,此时上下文切换到运行Stop
的线程,该线程执行到等待的Monitor.Enter
。此时UpdateViewSafe
再次开始运行。
我也看不到你的其他输出,但我想你会在中看到这一点
+++ UpdateViewSafe 1
+++ UpdateViewSafe 2
+++ Stop 1
+++ UpdateViewSafe 3
+++ UpdateViewSafe 4
+++ Stop 2
尽管最后两行很可能会被交换,这取决于你在调度器量子中的结局。它肯定可以以任何一种方式运行,所以不要依赖其中一种。
如果你想控制"停止1"输出,那么它需要在关键部分内——这就是关键部分的工作方式。