Зависание интерфейса WinForms при подключении через RDP

При подключении к запущенной программе через удалённый рабочий стол (RDP) интерфейс программы WinForms "намертво" зависает. Подобная проблема подробно описана в статье: https://ikriv.com/dev/dotnet/MysteriousHang Однако, данные из этой статьи лишь незначительно смогли мне помочь.

Причина в том, что я произвожу обновление элементов интерфейса из фонового потока через control.Invoke() или через контекст синхронизации SynchronizationContext, используя следующий статический метод:

public static void UIThread(SynchronizationContext uiContext, SendOrPostCallback code, object state=null, UIThreadMethod method=UIThreadMethod.async)
        {
            if (method==UIThreadMethod.sync)
                //код выполняется синхронно, а исключения передаются в вызывающий поток, а не в поток UI
                uiContext.Send(code, state);
            else
                //код выполняется асинхронно, а исключения передаются в UI поток
                uiContext.Post(code, state);
        }

Контекст синхронизации я захватываю в начале MainForm_Load() и записываю в статическое поле:

public static UIContext = SynchronizationContext.Current;

а затем, вызываю обновление контролов в параллельных потоках, например:

UIThread(UIContext, obj=>
            {
                pbIndicator.Image = AksSoft.Properties.Resources.ok_small;
                llRememberRefresh.Text = $"обновить";
                llRememberRefresh.Enabled = true;
            });

Всё работает нормально, до тех пор, пока я не подключаюсь через RDP.

Выяснил, что в момент подключения через RDP в программе срабатывает событие SystemEvents_UserPreferenceChanged, которое вызывает перерисовку контролов интерфейса и это вызывает зависание.

Помогли следующие шаги и статья https://exceptionshub.com/windows-form-application-freeze-randomly-when-run-overnight.html.

1) в файле Program.cs, до вызова Application.Run(new MainForm()) просто добавил подписку на событие:

SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;

обработчик события следующий:

static void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
        {
            //здесь ничего, просто пустой блок (!)
        }

2) Далее, в обработчике MainForm_Load() главного окна программы MainForm() делаю посылку системного сообщения:

SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, (IntPtr) Marshal.StringToHGlobalAnsi("Environment"), SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, MSG_TIMEOUT, out RESULT);

чтобы его отправить, требуется добавить следующий код, до обработчика MainForm_Load():

[Flags]
public enum SendMessageTimeoutFlags : uint
{
    SMTO_NORMAL = 0x0,
    SMTO_BLOCK = 0x1,
    SMTO_ABORTIFHUNG = 0x2,
    SMTO_NOTIMEOUTIFNOTHUNG = 0x8
}

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam, SendMessageTimeoutFlags fuFlags, uint uTimeout, out UIntPtr lpdwResult);
IntPtr HWND_BROADCAST = new IntPtr(0xffff);
const uint WM_WININICHANGE = 0x001A;
const uint WM_SETTINGCHANGE = WM_WININICHANGE;
const int MSG_TIMEOUT = 15000;

Обязательная отправка данного сообщения нужна для того, чтобы приложение принудительно начало обрабатывать пустой SystemEvents_UserPreferenceChanged(), а не тот, который вызывает системный код, приводящий к зависанию.

После запуска программы будет незначительная задержка отклика интерфейса, т.е. интерфейс отображается, но "оживает" и становится кликабельным через несколько секунд, однако, это рабочее решение, позволяющие избежать уродливого беспричинного зависания!

Если есть другие идеи как решить данную проблему, предлагайте, буду благодарен за любую помощь.


Ответы (0 шт):