我尝试在 dotnet 6 使用 File.Exists 判断管道是否存在,如果管道存在再进行连接。然而这个逻辑将会接下来的 NamedPipeClientStream 调用 Connect 连接失败

这个问题似乎是 CLR 底层的问题,我将问题报告给官方,请看 Using File.Exists to check the pipe created will make the NamedPipeClientStream connect fail · Issue #69604 · dotnet/runtime

使用 File.Exists 判断管道是否存在的代码如下

File.Exists(@"\\.\pipe\" + PipeName);

然而以上代码将会在 dotnet 6 下让 NamedPipeClientStream 调用 Connect 连接失败。这个逻辑如果放在 .NET Framework 下运行,是能成功的

最简复现步骤如下,先使用 NamedPipeServerStream 启动管道服务

void StartServer()
{
    var server = new NamedPipeServerStream(PipeName,
        PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024);

    server.BeginWaitForConnection(OnWaitForConnection, server);
}

接下来使用 File.Exists 判断管道是否存在

File.Exists(@"\\.\pipe\" + PipeName);

再使用 NamedPipeClientStream 进行连接

void StartClient()
{
    var localServer = ".";
    var pipeDirection = PipeDirection.InOut;
    var client = new NamedPipeClientStream(localServer,
        PipeName, pipeDirection, PipeOptions.Asynchronous);

    var timeout = 1000 * 5;
    client.Connect(timeout);
}

运行代码,可以看到 Connect 方法抛出 TimeoutException 错误

核心原因是在 .NET 6 通过 GetFileAttributesW 去判断管道是否存在,然而根据堆栈网 的描述,通过 GetFileAttributesW 去判断一个非文件系统的对象,会有非预期的行为。这也就是管道连接失败的原因。那为什么 .NET Framework 没问题?因为 .NET Framework 是先调用 FindFirstFile 进行判断

因此一个解决方法是采用和 .NET Framework 一样的 FindFirstFile 方法进行判断管道是否存在,代码如下

        private static bool IsPipeExists(string pipeName)
        {
            try
            {
                // 不要用 File.Exists 判断,内部会调用 GetFileAttributes 导致管道无法被连接

                unsafe
                {
                    // 这里是一个结构体,但是不关心内容,直接栈上分配点空间给它
                    var findFileData = stackalloc byte[604];

                    var file = FindFirstFile(@"\\.\pipe\" + pipeName, (IntPtr)findFileData);

                    const nint INVALID_HANDLE_VALUE = -1;

                    if (file != INVALID_HANDLE_VALUE)
                    {
                        FindClose(file);
                        return true;
                    }
                }
            }
            catch
            {
                
            }
            return false;
        }


        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "FindFirstFileW", ExactSpelling = true)]
        private static extern SafeFileHandle FindFirstFile([In] string lpFileName, [In] IntPtr lpFindFileData);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "FindClose", ExactSpelling = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FindClose([In] IntPtr hFindFile);

如果不想碰不安全代码,也可以采用判断文件夹里面的文件是否存在的方法判断管道是否存在

Directory.EnumerateFiles(@"\\.\pipe\", PipeName).Any();

只是文件夹的判断方法会比使用 FindFirstFile 的速度慢一点点,我测试大概是 3-5 毫秒左右

更多请看 Detecting that NamedPipe exists - Andrii Snihyr

本文以上的测试代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin e26bdd0bced93db8edf8be18f1062e571f3d1861

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 HallwhallkernarbafejaNakeldibi 文件夹


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-6-%E4%BD%BF%E7%94%A8-File.Exists-%E5%88%A4%E6%96%AD%E7%AE%A1%E9%81%93%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E5%B0%86%E8%AE%A9%E4%B8%8B%E6%AC%A1%E8%BF%9E%E6%8E%A5%E5%A4%B1%E8%B4%A5.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

微软最具价值专家


无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利