【问题标题】:Process.MainWindowHandle is non-zero in .NET Framework but zero in .NET Core unless debuggingProcess.MainWindowHandle 在 .NET Framework 中不为零,但在 .NET Core 中为零,除非调试
【发布时间】:2020-02-21 16:37:26
【问题描述】:

在 .NET Core (3.1) 中访问 MainWindowHandle 时,我遇到了 Process 类的奇怪行为。

考虑以下函数:

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。那时,如果我单步执行代码、设置更多断点等,都不再重要了;结果总是false

可能发生了什么,我该如何解决?

更多可能相关或不相关的细节:

  • 只要有 GUI(我最初是通过 WPF 应用程序发现的),其他进程也可能会出现同样的问题。例如,试试dfrgui
  • calc 等一些进程似乎为实际的 GUI 生成了一个单独的进程,因此行为略有变化:
    • 在 .NET Core 中,该函数仍返回 false,但 GUI 保持打开状态,断点技巧不再起作用。
    • 在 .NET Framework 中,由于进程已退出,InvalidOperationException 会在 MainWindowHandle 行中抛出。
  • 我正在使用 Visual Studio 2019 (16.4.5)、ReSharper 2019.3.2、.NET Core SDK 3.1.101 和 Windows 10 (Build 18363)。
  • 断点技巧也适用于 Rider (2019.3.3)。 但是, 仅当您在恢复程序执行之前留出足够的时间让主窗口出现时。在 Visual Studio 中也可能出现这种情况,但 IDE 反应太慢,我无法测试。

我猜调试器正在以某种方式改变程序的行为;列出所有进程属性时可能是偶然的,或者可能与它使用的线程有关。但具体如何?我可以复制同样的行为吗?

我已经尝试过但没有奏效的一些事情:

  • 增加尝试次数或尝试之间的睡眠时间
  • 重新排序 Refresh()Thread.Sleep()MainWindowHandle
  • Thread.Sleep() 调用替换为await Task.Delay()(并使函数异步)
  • UseShellExecute 设置为true / false 显式启动进程
  • 使用[MTAThread][STAThread] 属性
  • 在创建/刷新后通过反射打印所有进程属性
    • 即使这不起作用,调试器也可能以不同的方式/顺序读取这些属性,因此这可能仍然是调试产生影响的原因。

作为背景知识,我在使用 FlaUI 向我的 WPF 应用程序添加 UI 测试时遇到了这个问题(请参阅 the issue I created)。我现在很确定这不是图书馆本身的问题。它只是碰巧将Process.MainWindowHandle 用于它的一些方法。

【问题讨论】:

  • 有趣,不知道我是怎么错过的。不过,我看不到对此提出的问题/公关。我的 PR 缺少 EnsureState(State.HaveProcessInfo); 电话,因此我可能会对此进行更多调查,并在适用时添加它。

标签: c# wpf .net-core


【解决方案1】:

事实证明,这是由带有 .NET Core 本身的 issue 引起的。 MainWindowHandle 属性在第一次尝试后不会被重新评估,无论它是否返回 IntPtr.Zero

设置断点时,我唯一实现的就是延迟读取MainWindowHandle 的时刻。在此之前,我可以通过更长的Thread.Sleep() 通话来实现同样的效果。事实上,在我的情况下,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();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-10
    • 1970-01-01
    • 2013-09-02
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    相关资源
    最近更新 更多