【发布时间】: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行中抛出。
- 在 .NET Core 中,该函数仍返回
- 我正在使用 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);电话,因此我可能会对此进行更多调查,并在适用时添加它。