diff --git a/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs index 90421e8f..0de65ded 100644 --- a/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs +++ b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs @@ -1,4 +1,7 @@ -using dotnetCampus.Ipc.Context; +using System.IO; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; using dotnetCampus.Ipc.Pipes; namespace dotnetCampus.Ipc.Internals @@ -27,7 +30,38 @@ private void PeerProxy_PeerConnectionBroken(object? sender, IPeerConnectionBroke private async void Reconnect() { var ipcClientService = _ipcProvider.CreateIpcClientService(_peerProxy.PeerName); - await ipcClientService.Start(); + for (int i = 0; i < 4; i++) + { + try + { + await ipcClientService.Start(); + break; + } + catch (FileNotFoundException ex) + { + // ## 此异常有两种 + // + // 1. 一种来自于 namedPipeClientStream.ConnectAsync(),刚调用时还能获取到管道句柄,但马上与之连接时便已断开。 + // 2. 另一种来自 RegisterToPeer(),前面已经连上了,但试图发消息时便已断开。 + // + // ## 然而,为什么一连上就断开了呢? + // + // 这是因为每个端有两条管道,各自作为服务端和客户端。 + // 当重连时,靠的是服务端管道读到末尾来判断的;但此时重连的却是客户端。 + // 有极少情况下,这两条的断开时间间隔足够长到本方法的客户端已开始重连。 + // 那么,本方法的客户端在一开始试图连接对方时连上了,但随即就完成了之前没完成的断开,于是出现 FileNotFoundException。 + // + // ## 那么,如何解决呢? + // + // 通过重连,我们可以缓解因对方正在断开导致的我们误连。通过重连多次,可以更大概率缓解以至于解决此异常。 + // + // ## 是否有后续问题? + // + // 有可能本方法已全部完成之后才断开吗?不可能,因为 RegisterToPeer() 会发消息的,如果是对方进程退出等原因导致的断连,那么消息根本就无法发送。 + // 因为本调用内会置一个 TaskCompleteSource,所以也会导致一起等待此任务的其他发送全部失败,而解决方法就是在其他发送处也重试。 + await Task.Delay(16); + } + } _peerProxy.Reconnect(ipcClientService); } } diff --git a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs index 5c3b691f..767b7c0b 100644 --- a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs @@ -10,6 +10,7 @@ using dotnetCampus.Ipc.Diagnostics; using dotnetCampus.Ipc.Internals; using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils; using dotnetCampus.Ipc.Utils.Extensions; using dotnetCampus.Ipc.Utils.Logging; using dotnetCampus.Threading; @@ -85,29 +86,12 @@ public async Task Start(bool shouldRegisterToPeer = true) PipeOptions.None, TokenImpersonationLevel.Impersonation); _namedPipeClientStreamTaskCompletionSource = new TaskCompletionSource(); - // 带有重试的连接。 - for (int i = 0; i < 4; i++) - { - try - { #if NETCOREAPP - await namedPipeClientStream.ConnectAsync(); + await namedPipeClientStream.ConnectAsync(); #else - // 在 NET45 没有 ConnectAsync 方法 - await Task.Run(namedPipeClientStream.Connect); + // 在 NET45 没有 ConnectAsync 方法 + await Task.Run(namedPipeClientStream.Connect); #endif - break; - } - catch (FileNotFoundException ex) - { - // 因为每个端有两条管道,各自作为服务端和客户端。 - // 当重连时,靠的是服务端管道读到末尾来判断的;但此时重连的却是客户端。 - // 有极少情况下,这两条的断开时间间隔足够长到本方法的客户端已开始重连。 - // 那么,本方法的客户端在一开始试图连接对方时连上了,但随即就完成了之前没完成的断开,于是出现 FileNotFoundException。 - // 这时,我们通过重新连接一次可以再次等对方重新启动完再连。 - await Task.Delay(16); - } - } if (!_namedPipeClientStreamTaskCompletionSource.Task.IsCompleted) {