【问题标题】:Interface freezes in multi-threaded c# application多线程 C# 应用程序中的接口冻结
【发布时间】:2011-01-11 15:30:19
【问题描述】:

我有一个冻结界面的 c# .NET 多线程应用程序。不寻常的是,除非我让系统空闲足够长的时间以启动屏幕保护程序(这需要我重新输入密码才能重新获得对系统的访问权限),否则界面不会冻结。当界面再次可见时(在我成功输入密码后)界面被锁定。只要我不让屏保启动,界面就不会死机。

我应该指出,我有两个不同的可执行文件访问同一个 dll,无论我使用哪个应用程序访问 DLL,都会出现这个问题。这似乎暗示问题出在 DLL 中,因为这两个应用程序完全不同 (C++/MFC) 和 (C#/.NET),除了它们与 DLL 的关系。

两个 exe 执行与 DLL 交互的类似步骤。他们调用 dll 来设置串行端口通信,在 DLL 中打开一个状态窗口,在 DLL 中启动一个线程来监视通信端口,然后在主应用程序中启动一个线程来监视 dll 中的堆栈。

当 DLL 中的线程从通信端口获取数据时,对其进行解析并将其结果放入堆栈,然后通过委托发布到状态窗口。当 exe 中的线程看到堆栈中的数据时,它会在主窗口中输出数据,同样使用委托。

我发现,如果我将代码添加到 DLL 中的线程,使其每 30 秒调用一次 Application.DoEvents(),接口将冻结大约 30 秒,然后像正常一样恢复活动。 我认为某些东西阻塞了主线程并强制 DoEvents() 触发似乎打破了锁,但我不知道是什么导致了这个锁。

这个问题出现在我的开发机器和测试机器上。

我已尝试将数据输出完全删除到 DLL 内的状态窗口,但这没有任何区别。

我从事多线程编程多年,从未见过这样的事情;所以任何建议将不胜感激。

谢谢。

【问题讨论】:

  • 在调试器中暂停冻结的 UI 并查看调用堆栈。
  • 您是否尝试过仅从非 UI 线程调用 DLL 方法并编组到 UI 线程?
  • 这可能并不完全有帮助,但是您是否打开了任何在屏幕保护程序启动时会发生的省电功能? (我认为这可能会使端口进入休眠状态。)
  • 不管是什么,Application.DoEvents 都不是答案。
  • 似乎无法让它在我的开发中失败。今天机器。

标签: c# multithreading


【解决方案1】:

当您以非标准方式初始化用户界面时,SystemEvents 类通常会引发这个问题。特别是使用线程。启动你的程序,Debug + Break All,Debug + Windows + Threads。如果你看到一个名为“.NET SystemEvents”的线程,那么你肯定会遇到这个问题。

一些背景知识:SystemEvent 类同时支持控制台模式应用程序和 GUI 应用程序。对于后者,它应该在 UI 线程上触发其事件处理程序。第一次订阅它的一个事件时,它会创建一个不可见的帮助窗口来获取系统通知。它可以通过两种方式做到这一点,要么在调用线程上创建窗口,要么通过启动帮助线程。它根据 Thread.GetApartmentState() 的值做出决定。如果是 STA,那么它可以在调用线程上创建窗口,并且所有事件回调都可以正确编组到该线程。

如果您创建的 first 窗口不是在 UI 线程上创建的,则会出错。例如闪屏。该窗口可能包含对像 UserPreferenceChanged 这样的系统事件感兴趣的控件,因此它们可以正确地重新绘制自己。它现在使用辅助线程,并且任何事件都将从该辅助线程而不是 UI 线程触发。毒害在 UI 线程上运行的任何窗口。会话切换出锁定的工作站(包括屏幕保护程序)出于某种神秘原因很可能导致死锁。您可能还会看到偶尔的绘画事故,从错误的线程中使用窗口的结果不那么令人讨厌。

除了修复初始化顺序之外,一种解决方法是在创建 任何 窗口之前将其放入您的 Main() 方法中:

Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };

【讨论】:

  • 我可以很容易地将它添加到调用此 DLL 的 c#/.NET 应用程序中,但是对于调用此 DLL 的 C++/MFC 应用程序我该怎么办?
  • 呃,调用初始化函数? C++ 代码是否正确初始化线程(CoInitializeEx 选择 STA)?你最好在那个 init 函数中检查 Thread.CurrentThread.GetApartmentState()。
  • 我在第一次进入 DLL 时检查了当前线程的单元状态,它是为 STA 设置的。我还在 Application.Run() 之前将您在上面推荐的行添加到我的 C# exe 的主要方法中,它没有任何区别。当我启动我的 exe 时,我打开主窗体的窗口,然后当用户单击按钮时,调用 DLL 以打开 com 端口并启动线程。当用户选择执行模式时,exe会再次调用DLL,打开一个状态窗口,然后提醒dll中的线程开始收集数据。
  • 您是否在 DataReceived 事件处理程序中正确使用 BeginInvoke()?
  • 嗯,这是你的问题,使你的程序死锁。我看不到您的代码,很难诊断原因。覆盖表单中的 OnHandleCreated 方法并在其上设置断点,以便您可以查看它们是在哪个线程上创建的。使用相同的调试窗口。
【解决方案2】:

问题似乎与 ActiveX 控件有关,可能是在表单中使用不正确。我切换到在 .NET 中使用串行端口库,但无法重现我的问题。感谢大家,尤其是 Hans 的帮助。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题,因为我的电脑只是在屏幕保护程序启动或我锁定我的电脑并且显示器进入睡眠状态时挂断。 我 95% 确定我的多线程应用程序中出现了死锁。查看并确定您的代码中是否存在任何死锁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多