简体   繁体   English

SystemEvents.SessionSwitch 导致我的 Windows 窗体应用程序冻结

[英]SystemEvents.SessionSwitch causing my Windows Forms Application to freeze

I have a C# Windows Forms Application on .NET 4.5.我在 .NET 4.5 上有一个 C# Windows 窗体应用程序。

This application connects to a USB Device.此应用程序连接到 USB 设备。

I want to support multiple sessions at the same time.我想同时支持多个会话。

In order to do so, I need to disconnect from that device on session lock to allow the new session to connect to it.为此,我需要在会话锁定时与该设备断开连接,以允许新会话连接到它。

I used SystemEvents.SessionSwitchEventArgs.Reason to detect such events: - SessionSwitchReason.ConsoleDisconnect on session switch - SessionSwitchReason.ConsoleConnect on unlock after session switch我使用 SystemEvents.SessionSwitchEventArgs.Reason 来检测此类事件: - SessionSwitchReason.ConsoleDisconnect on session switch - SessionSwitchReason.ConsoleConnect on unlock after session switch

This event seemed like the perfect solution but sometimes at random times (after a number of locks or unlocks), the event doesn't get fired and the UI freezes.此事件似乎是完美的解决方案,但有时在随机时间(在多次锁定或解锁之后),该事件不会被触发并且 UI 会冻结。 It's worth noting that this doesn't happen when the application is running in the debugger.值得注意的是,当应用程序在调试器中运行时不会发生这种情况。

I know from the logs that some other background threads are still working normally but the UI freezes and the subscribed function to the event doesn't get called.我从日志中知道其他一些后台线程仍在正常工作,但 UI 冻结并且未调用事件的订阅函数。

A sample of my code:我的代码示例:

Program.cs:程序.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);
        }

    }

}

Note: I have not interest in SessionSwitchReason.SessionUnlock or SessionSwitchReason.SessionLock because both I don't want any action on session lock and unlock on the same session.注意:我对 SessionSwitchReason.SessionUnlock 或 SessionSwitchReason.SessionLock 不感兴趣,因为我都不希望在同一会话上对会话锁定和解锁进行任何操作。

Thanks for the support!感谢您的支持!

I found out where the bug was.我发现了错误在哪里。

In brief,简而言之,

Don't ever create a control on a background worker thread.永远不要在后台工作线程上创建控件。

In my code if I removed the SessionSwitch event subscription, the hang would still occur.在我的代码中,如果我删除了 SessionSwitch 事件订阅,挂起仍然会发生。 I was able to trace back the wait on the main thread to SystemSettingsChanging which is also a SystemEvent but which I do not control.我能够将主线程上的等待追溯到 SystemSettingsChanging,这也是一个 SystemEvent 但我无法控制。

After I almost gave up on trying to figure out this hang, I went to reading the code line by line which led me to discover that a Form (pop up) was being created on a background thread.在我几乎放弃试图找出这个挂起之后,我开始逐行阅读代码,这让我发现在后台线程上创建了一个表单(弹出窗口)。

This part of the code didn't get my attention as showed in the sample given above.如上面给出的示例所示,这部分代码没有引起我的注意。

initArandomBackgroundThread(); initArandomBackgroundThread();

To know more about this freeze you can head to Microsoft Support for extensive explanation.要了解有关此冻结的更多信息,您可以前往Microsoft 支持部门进行详细解释。

The low level cause of such freeze as Microsoft claims is微软声称的这种冻结的低级别原因

This occurs if a control is created on a thread which doesn't pump messages and the UI thread receives a WM_SETTINGCHANGE message.如果在不发送消息的线程上创建控件并且 UI 线程收到 WM_SETTINGCHANGE 消息,则会发生这种情况。

Common causes are a splash screens created on a secondary UI thread or any controls created on worker threads.常见原因是在辅助 UI 线程上创建的闪屏或在工作线程上创建的任何控件。

The fix修复

Applications should never leave Control objects on threads without an active message pump.应用程序不应将 Control 对象留在没有活动消息泵的线程上。 If Controls cannot be created on the main UI thread, they should be created on a dedicated secondary UI thread and Disposed as soon as they are no longer needed.如果无法在主 UI 线程上创建控件,则应在专用的辅助 UI 线程上创建它们,并在不再需要时立即处置。

Debugging调试

One way to identify which windows are created on which thread is with Spy++ in the Processes view (Spy.Processes menu).在进程视图(Spy.Processes 菜单)中使用 Spy++ 识别在哪个线程上创建哪些窗口的一种方法。 Select the hung process and expand its threads to see if there are any unexpected windows.选择挂起的进程并展开其线程以查看是否有任何意外窗口。 This will find the native window if it still exists;如果它仍然存在,这将找到本机窗口; however, the problem can occur even if the native window has been destroyed, so long as the managed Control has not yet been Disposed.但是,即使本机窗口已被破坏,只要托管控件尚未被处置,该问题也会发生。

I see that you are only subscribing to the SystemEvents.SessionSwitch event, but never unsubscribing.我看到您只订阅SystemEvents.SessionSwitch事件,但从未取消订阅。 Since SystemEvents.SessionSwitch is a STATIC event you must take great care to unsubscribe from it before your application exits.由于 SystemEvents.SessionSwitch 是一个 STATIC 事件,因此您必须在应用程序退出之前非常小心地取消订阅它。 If you do not unsubscribe then you leave the door open for memory leaks, which then can cause a chain reaction of odd failures.如果您不取消订阅,那么您就会为内存泄漏敞开大门,这可能会导致奇怪故障的连锁反应。 See the documentation warning:请参阅文档警告:

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

Because this is a static event, you must detach your event handlers when your application is disposed, or memory leaks will result.由于这是一个静态事件,您必须在处理应用程序时分离事件处理程序,否则将导致内存泄漏。

Also, it seems you are calling DisconnectFromDevice(); TerminateDeviceThread();另外,您似乎正在调用DisconnectFromDevice(); TerminateDeviceThread(); DisconnectFromDevice(); TerminateDeviceThread(); on the main UI thread, which could explain some freezing depending on what it is actually doing.在主 UI 线程上,这可以解释一些冻结,具体取决于它实际在做什么。 It would be good to show us what that code does to comment further on it.最好向我们展示该代码的作用以进一步对其进行评论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM