在.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行中引发了MainWindowHandle

我正在使用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()调用(并使函数异步)
UseShellExecute明确设置为true / false来启动该过程
使用[MTAThread][STAThread]属性
创建/刷新后,通过反射打印所有过程属性


即使这行不通,也许调试器也会以不同的方式/顺序读取这些属性,所以这仍然可能是调试有所作为的原因。





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

最佳答案

原来,这是由.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();
    }
}

关于c# - .NET Framework中的Process.MainWindowHandle非零,但.NET Core中的Process.MainWindowHandle非零,除非进行调试,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60342879/

10-12 06:23