如何使用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

如何使用Threading.Timer和Control.Invoke避免死锁

我需要定期更新UI,所以我决定使用Threading.Timer.

由于您正在使用Winforms,我建议您改用System.Windows.Forms.Timer。它使用UI消息泵,因此代码在与UI相同的线程中执行,这意味着:

  • 它是同步的
  • 您不需要使用Invoke来更新UI控件

您不会有很好的精度(~50毫秒(,但在大多数情况下,它应该足以进行UI更新。

旁注:使用lock(lockObject){ ... }而不是Monitor(几乎相同,但更容易使用,因为关键部分的范围具体化了(

由于我们没有看到StartStop是如何被调用的,因此很难准确地判断您将如何获得所拥有的输出。

很可能在UpdateViewSafe进入监视器之前调用并调度了Stop,但它没有得到时间间隔。这允许UpdateViewSafe执行到Sleep,此时上下文切换到运行Stop的线程,该线程执行到等待的Monitor.Enter。此时UpdateViewSafe再次开始运行。

我也看不到你的其他输出,但我想你会在中看到这一点

+++ UpdateViewSafe 1
+++ UpdateViewSafe 2
+++ Stop 1
+++ UpdateViewSafe 3
+++ UpdateViewSafe 4
+++ Stop 2

尽管最后两行很可能会被交换,这取决于你在调度器量子中的结局。它肯定可以以任何一种方式运行,所以不要依赖其中一种。

如果你想控制"停止1"输出,那么它需要在关键部分内——这就是关键部分的工作方式。