通过右键单击任务栏阻止WinForms中的死锁

时间:2009-12-16 23:45:33

标签: c# .net winforms deadlock

我的Windows C#/ .NET应用程序遇到了一个奇怪的问题。实际上它是一个GUI应用程序,我的工作是包含的网络组件,封装在一个程序集中。 我不知道主/ GUI应用程序的代码,我可以联系它的开发者。

现在,应用程序的UI具有“开始”和“停止”网络引擎的按钮。两个按钮都有效。 为了使我的组件线程安全,我使用三种方法锁定。我不希望客户端能够在Start()完成之前调用Stop()。另外还有一个轮询计时器。

我试图向你展示尽可能少的行并简化问题:

private Timer actionTimer = new Timer(new
                TimerCallback(actionTimer_TimerCallback),
                null, Timeout.Infinite, Timeout.Infinite);

public void Start()
{
 lock (driverLock)
 {
  active = true;
  // Trigger the first timer event in 500ms
  actionTimer.Change(500, Timeout.Infinite);
 }
}

private void actionTimer_TimerCallback(object state)
{
 lock (driverLock)
 {
  if (!active) return;
  log.Debug("Before event");
  StatusEvent(this, new StatusEventArgs()); // it hangs here
  log.Debug("After event");
  // Now restart timer
  actionTimer.Change(500, Timeout.Infinite);
 }
}

public void Stop()
{
 lock (driverLock)
 {
  active = false;
 }
}

以下是如何重现我的问题。正如我所说,启动和停止按钮都可以工作,但是如果按下Start(),并且在执行TimerCallback期间按,请按Stop(),这样可以防止TimerCallback返回。它完全挂在相同的位置,即StatusEvent。因此,锁永远不会释放,GUI也会挂起,因为调用Stop()方法无法继续。

现在我发现了以下内容:如果应用程序由于此“死锁”而挂起,并且用鼠标右键单击任务栏中的应用程序,它将继续。它只是按预期工作。有人对此有解释或更好的解决方案吗?

顺便说一句,我也尝试使用InvokeIfRequired,因为我不知道GUI应用程序的内部。如果我的StatusEvent会改变GUI中的某些内容,这是必要的。 由于我没有参考GUI控件,我使用(假设只有一个目标):

Delegate firstTarget = StatusEvent.GetInocationList()[0];
ISynchronizeInvoke syncInvoke = firstTarget.Target as ISynchronizeInvoke;
if (syncInvoke.InvokeRequired)
{
  syncInvoke.Invoke(firstTarget, new object[] { this, new StatusEventArgs() });
}
else
{
  firstTarget.Method.Invoke(firstTarget.Target, new object[] { this, new StatusEventArgs() });
}

这种方法没有改变问题。我想这是因为我在主应用程序的事件处理程序上调用,而不是在GUI控件上。那么主应用程序负责调用?但无论如何,AFAIK虽然不需要使用Invoke,但不会导致像这样的死锁,但(希望)会出现异常。

6 个答案:

答案 0 :(得分:8)

答案 1 :(得分:2)

在审核您的代码时会想到一些事情。第一件事是你在激活状态事件之前没有检查空委托。如果没有绑定到事件的侦听器,那么这将导致异常,如果没有捕获或处理,可能会在线程代码中引起奇怪的问题。

所以我第一件事就是这样:

if(StatusEvent != null)
{
  StatusEvent(this, new StatusEventArgs());
}

想到的另一件事是,也许你的锁以某种方式使你失败。你用什么类型的物品来锁?最简单的方法就是使用简单的“对象”,但是你必须确保你没有使用一个值类型(例如int,float等)来装箱锁定,因此从来没有真正建立锁定语句将框并创建一个新的对象实例。您还应该记住,锁只会保留“其他”线程。如果在同一个线程上调用,那么它将通过lock语句。

答案 2 :(得分:1)

如果您没有GUI的源代码(您可能应该这样做),可以使用Reflector对其进行反汇编。甚至还有一个生成源文件的插件,因此您可以在VS IDE中运行应用程序并设置断点。

答案 3 :(得分:1)

无法访问GUI源会使这更难,但这里的一般提示...... WinForm GUI不是托管代码,并且与.NET线程不能很好地混合。建议的解决方案是使用BackgroundWorker生成独立于WinForm的线程。一旦你在BackgroundWorker启动的线程中运行,你就可以使用纯托管代码了,你可以使用.NET的定时器和线程来完成任何事情。限制是您必须使用BackgroundWorker的事件将信息传递回GUI,而BackgroundWorker启动的线程无法访问Winform控件。

此外,在“开始”任务运行时,您可以禁用“停止”按钮,反之亦然。但是BackgroundWorker仍然是要走的路;这样,后台线程运行时WinForm不会挂起。

答案 4 :(得分:1)

是的,这是一个典型的死锁场景。 StatusEvent无法继续,因为它需要UI线程来更新控件。然而,UI线程卡住了,试图获取driverLock。由调用StatusEvent的代码持有。两个线程都不能继续。

打破锁定的两种方法:

  • StatusEvent代码可能不一定需要同步运行。使用BeginInvoke而不是Invoke。
  • UI线程可能不一定需要等待线程停止。您的主题可以稍后通知它。

您的片段中没有足够的上下文来决定哪一个更好。

请注意,您可能还会对计时器进行竞争,但在您的代码段中无法显示。但是在定时器停止后回调可能会运行一微秒。通过使用真正的线程而不是计时器回调来避免这种头痛。它可以通过在ManualResetEvent上调用WaitOne()来定期执行操作,并传递超时值。那个ManualResetEvent很好地表示线程停止。

答案 5 :(得分:0)

这里有一个疯狂的猜测:状态消息是否会导致其他应用程序调用您的停止任务?

我会在所有三种方法的开头放置调试内容,看看你是否自己陷入僵局。