我已经编写了一个C#程序来充当Mercurial的预提交钩子,但让它可靠地工作的唯一方法有点复杂,所以我肯定遗漏了一些东西.我在64位的Windows10专业版上运行的是TortoiseHg6.4.5,而该程序的目标是.NET 6.0.

目标是获取已更改文件的列表,并在提交之前处理它们.一开始,最明显的事情似乎奏效了:

IEnumerable<string> GetHgStatus()
{
    // never terminates when a large number of files have been changed, and if I
    // kill the hung hg.exe process there's nothing in the stdout/stderr streams.

    // run exe: hg status -n -m -a
    var psi = new ProcessStartInfo
    {
        // check status:
        // -n = don't output leading status indicator, eg. M | A | R
        // -m = show only modified files
        // -a = show only files that have been added
        FileName = config.HgPath,
        Arguments = $"status -n -m -a",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        WorkingDirectory = config.RepoPath,
    };
    var exe = Process.Start(psi);
    try
    {
        exe.WaitForExit(); // never returns
        while (!exe.StandardOutput.EndOfStream)
            yield return exe.StandardOutput.ReadLine();
    }
    finally
    {
        exe.Dispose();
    }
}

但是,如果更改的文件列表非常长,则等待永远不会结束.我在任务列表中看到一个hg.exe实例,分配了大约57-60MB,但什么也不做.如果我终止它,则不会向stdout或stderr输出任何结果.

从命令行运行此命令时,我从来没有遇到过问题,所以我try 在cmdshell 中运行它:

IEnumerable<string> GetHgStatus()
{
    // never terminates when a large number of files have been changed, and if I
    // kill the hung hg.exe process there's nothing in the stdout/stderr streams.
    // run exe: hg status -n -m -a
    var psi = new ProcessStartInfo
    {
        // check status:
        // -n = don't output leading status indicator, eg. M | A | R
        // -m = show only modified files
        // -a = show only files that have been added
        FileName = "cmd.exe",
        Arguments = $"/c \"{config.HgPath}\" status -n -m -a",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        WorkingDirectory = config.RepoPath,
    };
    var exe = Process.Start(psi);
    try
    {
        exe.WaitForExit(); // never returns
        while (!exe.StandardOutput.EndOfStream)
            yield return exe.StandardOutput.ReadLine();
    }
    finally
    {
        exe.Dispose();
    }
}

这与第一个版本具有完全相同的行为.如果我将CreateNoWindow选项切换为False,那么我可以看到HG创建了一个命令shell 窗口,然后它就停在那里.

让它可靠工作的唯一方法是放弃重定向stdout/stderr,并将输出重定向到一个临时文件,然后读入该文件:

IEnumerable<string> GetHgStatus()
{
    // have to loop catching file access exceptions until the file is accessible

    // run exe: hg status -n -m -a
    var output = Path.GetTempFileName();
    var psi = new ProcessStartInfo
    {
        // check status:
        // -n = don't output leading status indicator, eg. M | A | R
        // -m = show only modified files
        // -a = show only files that have been added
        FileName = "cmd.exe",
        Arguments = $"/c \"{config.HgPath}\" status -n -m -a > {output}",
        UseShellExecute = false,
        CreateNoWindow = true,
        WorkingDirectory = config.RepoPath,
    };
    var exe = Process.Start(psi);
    try
    {
        exe.WaitForExit();
        // have to call Close() then poll until file is accessible
        exe.Close();
        do
        {
            try
            {
                return File.ReadAllLines(output);
            }
            catch (IOException e) when (e.Message.StartsWith("The process cannot access the file"))
            {
                continue;
            }
        } while (true);
    }
    finally
    {
        exe.Dispose();
        File.Delete(output);
    }
}

所以有两个问题:

  1. 我错过了什么,我不能直接运行hg.exe并重定向它的标准输出,我必须通过一个shell 间接地做到这一点,然后它的输出被重定向到一个文件?
  2. 有没有更好的方法来等待或知道进程的打开文件句柄已经关闭,这样我就可以避免这种旋转循环?

推荐答案

Hg.exe是否在等待某个东西消耗其输出缓冲区,然后再写入更多内容?可能还需要在Process对象上设置一些其他属性,以告知它总是在sdout可用时立即读取它.

这里有一些例子...也许你需要在流水线上打ReadToEnd分.https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?view=net-7.0#remarks

Csharp相关问答推荐

C# Json重新初始化动态类型

Blazor:计算值或保留为默认值

C# uwp中的Win11启动屏幕剪辑工作方式不同

Regex在c#中完全匹配

EF Core判断是否应用了AsSplitQuery()

什么是通过反射创建类的泛型接口方法的正确方法?

AsNoTrackingWithIdentitySolutions()似乎不起作用?

S能够用DATETIME来计算,这有什么错呢?

有没有办法使.NET 6应用程序在特定的.NET 6运行时版本上运行

C#EF Core WHERE IN LINQ FROM LIST WITH.CONTAINS不返回任何内容

无法通过绑定禁用条目

获取具有AutoFaces的所有IOptions对象的集合

如何在ASP.NET Core8中启用REST应用程序的序列化?

如何将DotNet Watch与发布配置和传递给应用程序的参数一起使用?

如何使用用于VS代码的.NET Maui扩展在我的iOS/Android设备或模拟器上进行调试?

多个选项卡上的MudForm验证

如何在C#中正确类型化带有泛型的嵌套类

如何将默认区域性更改为fr-FR而不是en-US?

FakeItEasy自动嘲弄内容

如何在.NET8中使用Blazor Web App(WebAssembly)托管服务器端控制器?