【问题标题】:SystemEvents.SessionSwitch causing my Windows Forms Application to freezeSystemEvents.SessionSwitch 导致我的 Windows 窗体应用程序冻结
【发布时间】:2017-08-16 07:49:22
【问题描述】:

我有一个基于 .NET 4.5 的 C# Windows 窗体应用程序。

此应用程序连接到 USB 设备。

我想同时支持多个会话。

为此,我需要在会话锁定时断开与该设备的连接,以允许新会话连接到它。

我使用 SystemEvents.SessionSwitchEventArgs.Reason 来检测此类事件: - 会话切换时的 SessionSwitchReason.ConsoleDisconnect - SessionSwitchReason.ConsoleConnect 在会话切换后解锁

此事件似乎是完美的解决方案,但有时在随机时间(在多次锁定或解锁之后),该事件不会被触发并且 UI 会冻结。 值得注意的是,当应用程序在调试器中运行时,这不会发生。

我从日志中知道,其他一些后台线程仍在正常工作,但 UI 冻结并且未调用事件的订阅函数。

我的代码示例:

程序.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());
    }
}

}

我的程序引擎:

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 不感兴趣,因为我不希望在同一会话上对会话锁定和解锁进行任何操作。

感谢支持!

【问题讨论】:

  • 关于 SystemEvents 类引起的问题,请参阅 this answer 中的 CheckSystemEventsHandlersForFreeze() 函数,它可以帮助找到根本原因,即导致冻结的确切控件。

标签: c# .net multithreading session freeze


【解决方案1】:

我发现了错误在哪里。

简而言之,

永远不要在后台工作线程上创建控件。

在我的代码中,如果我删除了 SessionSwitch 事件订阅,挂起仍然会发生。我能够将主线程上的等待追溯到 SystemSettingsChanging,这也是一个 SystemEvent,但我无法控制。

在我几乎放弃尝试解决这个问题后,我开始逐行阅读代码,这让我发现正在后台线程上创建了一个表单(弹出窗口)。

这部分代码没有引起我的注意,如上面给出的示例所示。

initArandomBackgroundThread();

要了解有关此冻结的更多信息,您可以前往 Microsoft Support 获取详细说明。

微软声称的这种冻结的低级原因

如果在不发送消息的线程上创建控件并且 UI 线程接收到 WM_SETTINGCHANGE 消息,则会发生这种情况。

常见原因是在辅助 UI 线程上创建的闪屏或在工作线程上创建的任何控件。

修复

应用程序不应在没有活动消息泵的情况下将控制对象留在线程上。如果无法在主 UI 线程上创建控件,则应在专用的辅助 UI 线程上创建它们,并在不再需要它们时立即处置。

调试

在进程视图(Spy.Processes 菜单)中使用 Spy++ 识别在哪个线程上创建哪些窗口的一种方法。选择挂起的进程,展开它的线程,看看有没有意外的窗口。如果它仍然存在,这将找到本机窗口;但是,即使本机窗口已被销毁,只要托管的 Control 尚未被 Disposed,也可能会出现问题。

【讨论】:

  • 关于 SystemEvents 类引起的问题,请参阅 this answer 中的 CheckSystemEventsHandlersForFreeze() 函数,它可以帮助找到根本原因,即在错误的线程上创建了哪些特定控件,从而导致冻结。
【解决方案2】:

我看到您只是订阅SystemEvents.SessionSwitch 事件,但从未取消订阅。由于 SystemEvents.SessionSwitch 是一个 STATIC 事件,因此您必须非常小心地在应用程序退出之前取消订阅它。如果您不取消订阅,那么您就会为内存泄漏敞开大门,这可能会导致奇怪故障的连锁反应。请参阅文档警告:

https://msdn.microsoft.com/en-us/library/microsoft.win32.systemevents.sessionswitch(v=vs.110).aspx

因为这是一个静态事件,所以在释放应用程序时必须分离事件处理程序,否则会导致内存泄漏。

此外,您似乎在主 UI 线程上调用 DisconnectFromDevice(); TerminateDeviceThread();,这可能会根据实际执行的操作来解释一些冻结。最好向我们展示该代码所做的进一步评论。

【讨论】:

  • 您对静态事件有一个很好的看法,在我的真实代码中,我正在取消订阅该事件。
  • 此外,所有形式的耗时操作都不应该在主 UI 线程上完成,例如 DisconnectFromDevice();终止设备线程();这些函数在新创建的后台线程上异步执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-10
  • 2015-08-18
  • 1970-01-01
相关资源
最近更新 更多