简体   繁体   中英

SystemEvents.SessionSwitch causing my Windows Forms Application to freeze

I have a C# Windows Forms Application on .NET 4.5.

This application connects to a USB Device.

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

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. 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.

A sample of my code:

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);
        }

    }

}

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.

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. 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.

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();

To know more about this freeze you can head to Microsoft Support for extensive explanation.

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.

Common causes are a splash screens created on a secondary UI thread or any controls created on worker threads.

The fix

Applications should never leave Control objects on threads without an active message pump. 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.

Debugging

One way to identify which windows are created on which thread is with Spy++ in the Processes view (Spy.Processes menu). 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. Since SystemEvents.SessionSwitch is a STATIC event you must take great care to unsubscribe from it before your application exits. 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

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(); on the main UI thread, which could explain some freezing depending on what it is actually doing. It would be good to show us what that code does to comment further on it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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