我尝试在 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
本文以上的测试代码放在github 和 gitee 欢迎访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 文件夹