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

时间:2014-03-11 12:14:13

标签: c# multithreading winforms timer compact-framework

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

2 个答案:

答案 0 :(得分:1)

  

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

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

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

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

附注:使用lock(lockObject){ ... }代替Monitor(几乎相同但更容易使用,因为关键部分范围已实现)

答案 1 :(得分:0)

由于我们没有看到如何调用StartStop,因此很难确切知道如何获得输出。

Stop可能会在UpdateViewSafe进入监视器之前调用并安排UpdateViewSafe,但它没有得到时间片。这允许Sleep执行到Stop,此时上下文切换到运行Monitor.Enter的线程,该线程执行到它等待的UpdateViewSafe。此时+++ UpdateViewSafe 1 +++ UpdateViewSafe 2 +++ Stop 1 +++ UpdateViewSafe 3 +++ UpdateViewSafe 4 +++ Stop 2 再次开始运行。

我也没有看到你输出的其余部分,但我猜你总能看到这个

{{1}}

尽管最后两行很可能会被交换,这取决于你最终如何调度量程。它绝对可以以任何一种方式运行,所以不要依赖于其中一种。

如果你想控制“Stop 1”输出,那么它需要在关键部分内 - 这就是关键部分的工作原理。