簡體   English   中英

SystemEvents.SessionSwitch 導致我的 Windows 窗體應用程序凍結

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

我在 .NET 4.5 上有一個 C# Windows 窗體應用程序。

此應用程序連接到 USB 設備。

我想同時支持多個會話。

為此,我需要在會話鎖定時與該設備斷開連接,以允許新會話連接到它。

我使用 SystemEvents.SessionSwitchEventArgs.Reason 來檢測此類事件: - SessionSwitchReason.ConsoleDisconnect on session switch - SessionSwitchReason.ConsoleConnect on unlock after session switch

此事件似乎是完美的解決方案,但有時在隨機時間(在多次鎖定或解鎖之后),該事件不會被觸發並且 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 不感興趣,因為我都不希望在同一會話上對會話鎖定和解鎖進行任何操作。

感謝您的支持!

我發現了錯誤在哪里。

簡而言之,

永遠不要在后台工作線程上創建控件。

在我的代碼中,如果我刪除了 SessionSwitch 事件訂閱,掛起仍然會發生。 我能夠將主線程上的等待追溯到 SystemSettingsChanging,這也是一個 SystemEvent 但我無法控制。

在我幾乎放棄試圖找出這個掛起之后,我開始逐行閱讀代碼,這讓我發現在后台線程上創建了一個表單(彈出窗口)。

如上面給出的示例所示,這部分代碼沒有引起我的注意。

initArandomBackgroundThread();

要了解有關此凍結的更多信息,您可以前往Microsoft 支持部門進行詳細解釋。

微軟聲稱的這種凍結的低級別原因

如果在不發送消息的線程上創建控件並且 UI 線程收到 WM_SETTINGCHANGE 消息,則會發生這種情況。

常見原因是在輔助 UI 線程上創建的閃屏或在工作線程上創建的任何控件。

修復

應用程序不應將 Control 對象留在沒有活動消息泵的線程上。 如果無法在主 UI 線程上創建控件,則應在專用的輔助 UI 線程上創建它們,並在不再需要時立即處置。

調試

在進程視圖(Spy.Processes 菜單)中使用 Spy++ 識別在哪個線程上創建哪些窗口的一種方法。 選擇掛起的進程並展開其線程以查看是否有任何意外窗口。 如果它仍然存在,這將找到本機窗口; 但是,即使本機窗口已被破壞,只要托管控件尚未被處置,該問題也會發生。

我看到您只訂閱SystemEvents.SessionSwitch事件,但從未取消訂閱。 由於 SystemEvents.SessionSwitch 是一個 STATIC 事件,因此您必須在應用程序退出之前非常小心地取消訂閱它。 如果您不取消訂閱,那么您就會為內存泄漏敞開大門,這可能會導致奇怪故障的連鎖反應。 請參閱文檔警告:

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

由於這是一個靜態事件,您必須在處理應用程序時分離事件處理程序,否則將導致內存泄漏。

另外,您似乎正在調用DisconnectFromDevice(); TerminateDeviceThread(); DisconnectFromDevice(); TerminateDeviceThread(); 在主 UI 線程上,這可以解釋一些凍結,具體取決於它實際在做什么。 最好向我們展示該代碼的作用以進一步對其進行評論。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM