我在.NET 4.5上有一个C#Windows窗体应用程序。
此应用程序连接到USB设备。
我想同时支持多个会话。
为了做到这一点,我需要在会话锁定时断开与该设备的连接以允许新会话连接到它。
我使用SystemEvents.SessionSwitchEventArgs.Reason来检测此类事件: - 会话切换时的SessionSwitchReason.ConsoleDisconnect - 会话切换后解锁时SessionSwitchReason.ConsoleConnect
此事件似乎是完美的解决方案,但有时在随机时间(在多次锁定或解锁后),事件不会被触发并且UI冻结。 值得注意的是,当应用程序在调试器中运行时,不会发生这种情况。
我从日志中知道其他一些后台线程仍然正常工作但是UI冻结了,并且没有调用事件的订阅函数。
我的代码示例:
的Program.cs:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MyProgram
{
static class Program
{
private static Mutex mutex = null;
[STAThread]
static void Main()
{
const string appName = "MyProgram";
bool createdNew;
mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
//app is already running! Exiting the application
return;
}
Application.EnableVisualStyles();
//This was one attempt to solve the UI deadlock Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
Application.SetCompatibleTextRenderingDefault(false);
MyProgramEngine MyProgramEngine = new MyProgram.MyProgramEngine();
Application.Run(MyProgramEngine.getForm());
}
}
}
MyProgramEngine:
using log4net;
using log4net.Config;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using WindowsInput;
using System.Windows.Forms;
using Microsoft.Win32;
namespace MyProgram
{
class MyProgramEngine
{
private MainForm mainForm;
public MyProgramEngine()
{
XmlConfigurator.Configure();
Utility.logger.Info(string.Format("MyProgram Started. Version: {0}", Application.ProductVersion));
SystemEvents.SessionSwitch += new SessionSwitchEventHandler(SystemEvents_SessionSwitch);
if (!GlobalSettings.getInstance().isProperlyConfigured())
{
WarningForm warningForm = new WarningForm("MyProgram is not properly configured. Please contact support");
warningForm.ShowDialog();
Application.Exit();
Environment.Exit(0);
}
mainForm = new MainForm();
initArandomBackgroundThread();
initDeviceThread();
}
private void initDeviceThread()
{
Thread detectAndStartReader = new Thread(initDevice);
detectAndStartReader.IsBackground = true;
detectAndStartReader.Start();
}
public void initDevice()
{
//Connect to device
//Start device thread
}
public MainForm getForm()
{
return mainForm;
}
//Handles session switching events
internal void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
try
{
if (e.Reason.Equals(SessionSwitchReason.ConsoleDisconnect))
{
DisconnectFromDevice();
TerminateDeviceThread();
}
else if (e.Reason.Equals(SessionSwitchReason.ConsoleConnect))
{
initDeviceThread();
}
else
{
Utility.logger.Info("The following SesseionSwitchReason has been caught: " + e.Reason + " , No action!");
}
}
catch (Exception ex)
{
Utility.logger.Error("Something bad happened while managing session switching events", ex);
}
}
}
注意:我对SessionSwitchReason.SessionUnlock或SessionSwitchReason.SessionLock不感兴趣,因为我不想在同一会话中对会话锁定和解锁进行任何操作。
感谢您的支持!
答案 0 :(得分:3)
我发现了这个错误的位置。
简而言之,
不要在后台工作线程上创建控件。
在我的代码中,如果我删除了SessionSwitch事件订阅,则仍会发生挂起。我能够将主线程上的等待追溯到SystemSettingsChanging,这也是一个SystemEvent但我无法控制。
在我几乎放弃试图找出这个挂起之后,我逐行阅读了代码,这让我发现在后台线程上创建了一个Form(弹出窗口)。
这部分代码没有引起我的注意,如上面给出的样本所示。
initArandomBackgroundThread();
要了解有关此冻结的更多信息,您可以前往Micorsoft Support进行广泛的解释。
此类冻结的低级别原因与Microsoft声称的一样
如果在不抽取消息且UI线程收到WM_SETTINGCHANGE消息的线程上创建控件,则会发生这种情况。
常见原因是在辅助UI线程上创建的启动屏幕或在工作线程上创建的任何控件。
修复
应用程序不应该在没有活动消息泵的情况下将Control对象留在线程上。如果无法在主UI线程上创建控件,则应在专用辅助UI线程上创建控件,并在不再需要时立即对其进行处理。
<强>调试强>
在进程视图(Spy.Processes菜单)中识别在哪个线程上使用Spy ++创建哪些窗口的一种方法。选择挂起的进程并展开其线程以查看是否存在任何意外窗口。如果它仍然存在,这将找到本机窗口;但是,即使本机窗口已被销毁,只要托管控件尚未处理,问题就会发生。
答案 1 :(得分:0)
我看到您只订阅SystemEvents.SessionSwitch
事件,但从未取消订阅。由于SystemEvents.SessionSwitch是STATIC事件,因此在应用程序退出之前必须非常小心地取消订阅。如果您没有取消订阅,那么您将门打开以进行内存泄漏,这可能会导致奇怪故障的连锁反应。请参阅文档警告:
https://msdn.microsoft.com/en-us/library/microsoft.win32.systemevents.sessionswitch(v=vs.110).aspx
因为这是一个静态事件,所以在处理应用程序时必须分离事件处理程序,否则会导致内存泄漏。
此外,您似乎在主UI线程上调用DisconnectFromDevice(); TerminateDeviceThread();
,这可以解释一些冻结,具体取决于它实际上在做什么。最好向我们展示该代码对其进行进一步评论的作用。