Process.MainWindowHandle не равен нулю в. NET Framework, но равен нулю в. NET Core, если не происходит отладка - PullRequest
3 голосов
/ 21 февраля 2020

Я сталкиваюсь со странным поведением класса Process при доступе к MainWindowHandle in. NET Core (3.1).

Рассмотрим следующую функцию:

bool TestProcess()
{
    var process = Process.Start("notepad");
    try
    {
        for (var attempt = 0; attempt < 20; ++attempt)
        {
            process?.Refresh();
            if (process?.MainWindowHandle != IntPtr.Zero)
            {
                return true;
            }

            Thread.Sleep(100);
        }

        return false;
    }
    finally
    {
        process?.Kill();
    }
}

Если функция запускается в. NET Framework, она возвращает true, как я и ожидал. Однако при использовании. NET Core (3.1 в моем случае) вместо него возвращается false.

Теперь часть, которая озадачивает меня еще больше:

  • Если я установить точку останова в любом месте после запуска процесса, но до того, как будет прочитано свойство MainWindowHandle (или, если я просто перейду код хотя бы один раз между этими строками), функция вернет true.
  • Если я установите точку останова после чтения свойства MainWindowHandle, функция вернет false. На этом этапе больше не имеет значения, перехожу ли я по коду, устанавливаю больше точек останова и т. Д. c; результат всегда false.

Что может происходить и как я могу это исправить?

Еще несколько деталей, которые могут или не могут быть быть релевантным:

  • Та же проблема может возникнуть с другими процессами, если они имеют GUI (я первоначально обнаружил его с помощью приложения WPF). Вместо этого попробуйте, например, dfrgui.
  • Некоторые процессы, такие как calc, по-видимому, порождают отдельный процесс для фактического GUI, поэтому поведение немного меняется:
    • In. NET Core, функция по-прежнему возвращает false, но GUI остается открытым, и трюк с точкой останова больше не работает.
    • In. NET Framework, InvalidOperationException выбрасывается в * Линия 1042 * из-за того, что процесс уже завершился.
  • Я использую Visual Studio 2019 (16.4.5), ReSharper 2019.3.2,. NET Core SDK 3.1.101 и Windows 10 (сборка 18363).
  • Трюк с точкой останова также работает в Rider (2019.3.3). Однако, только если вы оставляете достаточно времени для отображения главного окна перед возобновлением выполнения программы. Это также может иметь место в Visual Studio, но среда IDE реагирует слишком медленно, чтобы я мог ее протестировать.

Я предполагаю, что отладчик каким-то образом изменяет поведение программы; возможно случайно при перечислении всех свойств процесса, или возможно это связано с потоками, которые он использует. А как именно? Могу ли я повторить такое же поведение?

Некоторые вещи, которые я уже пробовал, но не работали:

  • Увеличение количества попыток или времени ожидания между попытками
  • Изменение порядка строк Refresh(), Thread.Sleep() и MainWindowHandle
  • Замена вызова Thread.Sleep() на await Task.Delay() (и выполнение функции asyn c)
  • Начать процесс с UseShellExecute установлен в true / false явно
  • Использование [MTAThread] или [STAThread] атрибутов
  • Печать всех свойств процесса с помощью отражения после его создания / обновления
    • Даже если это не сработало, возможно, отладчик считывает эти свойства в другом порядке / порядке, поэтому, возможно, именно поэтому отладка имеет значение.

В качестве предыстории я столкнулся с этой проблемой, когда добавил тесты пользовательского интерфейса в свое приложение WPF с помощью FlaUI (см. проблему, которую я создал ). Теперь я совершенно уверен, что это не проблема самой библиотеки; он просто использует Process.MainWindowHandle для некоторых своих методов.

1 Ответ

2 голосов
/ 22 февраля 2020

Оказывается, это было вызвано проблемой с. NET Самым ядром. Свойство MainWindowHandle не будет переоцениваться после первой попытки, независимо от того, вернулось ли оно IntPtr.Zero.

При установке точек останова единственное, чего я достиг, - это отсрочить момент, в который MainWindowHandle читается. Я мог бы добиться того же с более длинным Thread.Sleep() звонком до этого. На самом деле, в моем случае для Notepad было достаточно 100 миллисекунд, но для исходного приложения WPF, которое я тестировал, мне нужно около 1 секунды. Возможно, я слишком настороженно относился к отладчикам.

Я уже отправил запрос на удаление, чтобы это исправить. В то же время, если на кого-то также влияет что-то подобное, я бы рекомендовал заменить любой вызов Refresh() на process = Process.GetProcessById(process.Id). Это вернет новый экземпляр Process, указывающий на тот же процесс, и, следовательно, свойство MainWindowHandle может быть переоценено без проблем.

В моем исходном примере это будет выглядеть так (переупорядочено немного чтобы избежать первоначального создания экземпляра):

bool TestProcess()
{
    var process = Process.Start("notepad");
    try
    {
        for (var attempt = 0; attempt < 20; ++attempt)
        {
            if (process?.MainWindowHandle != IntPtr.Zero)
            {
                return true;
            }

            Thread.Sleep(100);
            process = Process.GetProcessById(process.Id);
        }

        return false;
    }
    finally
    {
        process?.Kill();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...