c# - Process.MainWindowHandle 在 .NET Framework 中不为零,但在 .NET Core 中为零,除非调试
问题描述
在 .NET Core (3.1) 中Process
访问时,我面临着类的奇怪行为。MainWindowHandle
考虑以下函数:
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
,因此会在行中抛出an 。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
使用set totrue
/false
显式启动该过程- 使用
[MTAThread]
或[STAThread]
属性 - 创建/刷新后通过反射打印所有进程属性
- 即使这不起作用,调试器也可能以不同的方式/顺序读取这些属性,所以这可能仍然是调试有所作为的原因。
作为背景知识,当我使用 FlaUI 向我的 WPF 应用程序添加 UI 测试时遇到了这个问题(请参阅我创建的问题)。我现在很确定这不是图书馆本身的问题。它只是碰巧用于它Process.MainWindowHandle
的一些方法。
解决方案
事实证明,这是由.NET Core 本身的问题引起的。在第一次尝试后,该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();
}
}
推荐阅读
- .net - 带有设置 VB.Net 的 Nreco ConvertMedia
- c - 为什么一个 char[512] 的地址等于那个 char[512]
- javascript - 如果页面加载后由js创建,如何在html上运行eventlistener?
- javascript - 我的 webgl 代码中的表面消除不正确
- google-sheets-api - 用于查找单元格是否为空白的 Google 脚本 - isBlank 函数不起作用
- svn - SVN 提交后导入
- python - 在python中检索字典字符的平均长度
- c - c使用fgets读取文件,strtok导致分段错误
- c# - 我的基础实体未在扩展方法中加载的原因可能是什么?
- r - 将 geom_point 与 geom_polygon 分组