C# Process.MainWindowHandle在.NET Framework中为非零,但在.NET Core中为零,除非进行调试

C# Process.MainWindowHandle在.NET Framework中为非零,但在.NET Core中为零,除非进行调试,c#,wpf,.net-core,C#,Wpf,.net Core,在访问.NETCore(3.1)中的MainWindowHandle时,我面临进程类的一种奇怪行为 考虑以下功能: bool TestProcess() { var process = Process.Start("notepad"); try { for (var attempt = 0; attempt < 20; ++attempt) { process?.Refresh(); i

在访问.NETCore(3.1)中的
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();
    }
}
bool TestProcess()
{
var process=process.Start(“记事本”);
尝试
{
对于(var尝试=0;尝试<20;++尝试)
{
进程?.Refresh();
if(进程?.MainWindowHandle!=IntPtr.Zero)
{
返回true;
}
睡眠(100);
}
返回false;
}
最后
{
进程?.Kill();
}
}
如果该函数在.NET Framework中运行,它会像我预期的那样返回
true
。但是,当使用.NETCore(在我的例子中是3.1)时,它会返回
false

现在更让我困惑的是:

  • 如果我在进程启动后但在读取
    MainWindowHandle
    属性之前的任何位置设置断点(或者如果我只是在这些行之间至少跨代码一次),函数将返回
    true
  • 如果在读取
    MainWindowHandle
    属性后设置断点,函数将返回
    false
    。在这一点上,如果我跳过代码,设置更多断点,等等,这就不再重要了。;结果总是
    false
可能发生的情况以及我如何修复它?

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

  • 同样的问题也可能发生在其他进程上,只要它们有GUI(我最初是通过WPF应用程序发现的)。例如,尝试使用
    dfrgui
  • 一些进程,如
    calc
    似乎会为实际GUI生成一个单独的进程,因此行为会发生轻微变化:
    • 在.NETCore中,函数仍然返回
      false
      ,但GUI保持打开状态,断点技巧不再起作用
    • 在.NET Framework中,由于进程已退出,
      MainWindowHandle
      行中抛出了一个
      InvalidOperationException
  • 我使用的是Visual Studio 2019(16.4.5)、ReSharper 2019.3.2、.NET Core SDK 3.1.101和Windows 10(Build 18363)
  • 断点技巧也适用于Rider(2019.3.3)但是,仅当您在继续执行程序之前留出足够的时间让主窗口出现时。VisualStudio中可能也是这样,但IDE的反应太慢,我无法测试
我猜调试器正在以某种方式改变程序的行为;可能是在列出所有进程属性时偶然发生的,也可能与它使用的线程有关。但具体如何?我可以复制同样的行为吗

一些我已经尝试过但不起作用的事情:

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


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

事实证明,这是由.NET内核本身的错误引起的。
MainWindowHandle
属性在第一次尝试后不会重新计算,无论它是否返回
IntPtr.Zero

设置断点时,我唯一能做到的就是延迟读取
MainWindowHandle
的时间。如果在此之前调用更长的
Thread.Sleep()
,我也可以达到同样的效果。事实上,在我的例子中,100毫秒对于记事本来说已经足够了,但是对于我正在测试的原始WPF应用程序,我需要大约1秒的时间。也许我对调试器太过谨慎了

我已经提交了一个请求来解决这个问题。同时,如果任何人也受到类似的影响,我建议将任何
Refresh()
调用替换为
process=process.GetProcessById(process.Id)
。这将返回一个指向同一进程的新的
进程
实例,因此可以重新计算
MainWindowHandle
属性而不会出现问题

在我最初的示例中,它看起来是这样的(重新排序以避免初始实例创建):

bool TestProcess()
{
var process=process.Start(“记事本”);
尝试
{
对于(var尝试=0;尝试<20;++尝试)
{
if(进程?.MainWindowHandle!=IntPtr.Zero)
{
返回true;
}
睡眠(100);
process=process.GetProcessById(process.Id);
}
返回false;
}
最后
{
进程?.Kill();
}
}

很有趣,不知道我怎么会错过。不过,我看不到有人就此提出任何问题/公关。我的PR缺少
EnsureRate(State.HaveProcessInfo)调用,因此我可能会对此进行更多的研究,并在适用的情况下添加它。
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();
    }
}