我几个月来一直在尝试解决 Balzor 服务器应用程序中的服务器超时断开连接问题。 我尝试了很多不同的方法,但到目前为止没有任何效果。 我刚刚完成应用程序,但无法修复这些断开连接。
客户端应用程序认为它尚未收到来自服务器的消息,并且每隔 30 秒左右就会出现一次“尝试重新连接到服务器:1 of 8”开始。 它每次都会成功重新连接,但我需要这些重新连接消息来停止。
这是我尝试失败的 SignalR 配置示例。
Blazor.start({
reconnectionOptions: {
maxRetries: 100,
retryIntervalMilliseconds: 2000
},
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 30000;
c.keepAliveIntervalInMilliseconds = 10000;
builder.build = () => {
return c;
};
}
});
通过调整此代码中的持续时间,我可以更改断开连接的频率,但它们仍然会发生。
我决定向客户端发送我自己的“FakeKeepAlive”消息。
private async Task StartFakeKeepAlive(CancellationToken ct)
{
try
{
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(Utils.GlobalKeepAliveIntervalInMilliseconds)))
{
while (!ct.IsCancellationRequested)
{
await timer.WaitForNextTickAsync(ct);
now = DateTime.Now;
Debug.WriteLine($"FakeKeepAlive {now.ToString("h:mm:ss.FFF")} ");
await JS.InvokeVoidAsync("console.log", "CSDKeepAlive: At ", DateTime.Now.ToString("h:mm:ss.FFF"));
await InvokeAsync(StateHasChanged);
}
}
}
catch (Exception ex)
{
logger.Warn(ex, "StartFakeKeepAlive() exception");
throw;
}
}
请注意,我尝试了 InvokeAsync(StateHasChanged) 和 JS Interop 来向客户端发送 console.log 消息以及这两者。
这有助于延迟断开连接的开始,但大约 5 分钟后断开连接再次出现。
这是 Chrome 控制台输出:
[2024-04-15T20:21:24.628Z] Information: Normalizing '_blazor' to 'https://localhost:5001/_blazor'. blazor.server.js:1
[2024-04-15T20:21:24.673Z] Information: WebSocket connected to wss://localhost:5001/_blazor?id=fulG9OiaSbyn6AO1yCwIwQ. blazor.server.js:1
CSDKeepAlive: At 4:22:01.339 blazor.server.js:1
CSDKeepAlive: At 4:22:31.333 blazor.server.js:1
CSDKeepAlive: At 4:23:01.331 blazor.server.js:1
CSDKeepAlive: At 4:23:31.326 blazor.server.js:1
CSDKeepAlive: At 4:24:01.331 blazor.server.js:1
CSDKeepAlive: At 4:24:31.328 blazor.server.js:1
CSDKeepAlive: At 4:25:01.331 blazor.server.js:1
CSDKeepAlive: At 4:25:31.327 blazor.server.js:1
CSDKeepAlive: At 4:26:01.329 blazor.server.js:1
CSDKeepAlive: At 4:26:31.327 blazor.server.js:1
CSDKeepAlive: At 4:27:01.329 blazor.server.js:1
CSDKeepAlive: At 4:27:31.339 blazor.server.js:1
CSDKeepAlive: At 4:28:01.327 blazor.server.js:1
[2024-04-15T20:28:31.332Z] Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message from the server.'.
log @ blazor.server.js:1
_stopConnection @ blazor.server.js:1
features.reconnect.transport.onclose @ blazor.server.js:1
_close @ blazor.server.js:1
stop @ blazor.server.js:1
_stopInternal @ blazor.server.js:1
await in _stopInternal (async)
stop @ blazor.server.js:1
serverTimeout @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
setTimeout (async) _resetTimeoutPeriod @ blazor.server.js:1
_processIncomingData @ blazor.server.js:1
Xt.connection.onreceive @ blazor.server.js:1
s.onmessage @ blazor.server.js:1
blazor.server.js:1
[2024-04-15T20:28:34.341Z] Information: Normalizing '_blazor' to 'https://localhost:5001/_blazor'. blazor.server.js:1
[2024-04-15T20:28:34.354Z] Information: WebSocket connected to wss://localhost:5001/_blazor?id=VbjkvE9CAzGk89_vdVcqng. blazor.server.js:1
[2024-04-15T20:29:07.836Z] Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message from the server.'.
{Details omitted, but same as above}
[2024-04-15T20:29:10.843Z] Information: Normalizing '_blazor' to 'https://localhost:5001/_blazor'. blazor.server.js:1
[2024-04-15T20:29:10.850Z] Information: WebSocket connected to wss://localhost:5001/_blazor?id=5MLFdZ6qPPwEdrn5djWidg.
这是program.cs中的相关代码。 请注意,注释掉的代码是尝试在此处设置保持活动间隔。 上面的示例使用默认的 ServerTimeout 和 KeepAliveIntervals。
//builder.Services.AddServerSideBlazor().AddHubOptions(option =>
// {
// option.KeepAliveInterval = TimeSpan.FromMilliseconds(Utils.GlobalKeepAliveIntervalInMilliseconds);
// });
builder.Services.AddServerSideBlazor().AddCircuitOptions(option =>
{
option.DetailedErrors = true;
});
这是我的 .csproj 文件中的整个包部分。
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="6.15.0" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.8" />
<PackageReference Include="Radzen.Blazor" Version="4.24.5" />
<PackageReference Include="Versus_NAPI_Core" Version="4.0.0-20230918.7" />
</ItemGroup>enter image description here
这是其余的依赖项:
我几周来一直在努力解决这个问题。我相信我已经阅读了所有可能与此内容以及所有 Microsoft 文档有关的 Stack Overflow 帖子。 我的经历与我读过的所有内容都不一致,所以我想知道我是否遗漏了更深层的东西。
我无法辨别为什么断开连接仅在 5 分钟后才开始,以及为什么我的“FakeKeepAlive”消息在连接重置后没有收到。
无论如何,我在 .NET 7.0 中启动了该应用程序,但升级到了 .NET 8.0。 这个问题在我升级之前就出现了。
升级至 Microsoft 支持后,发现问题出在我们系统使用的 dll 库,该库最初是作为 WinForms 控件实现的。
Per Microsoft...“似乎构建 WinForms 控件会在当前线程上设置 WindowsFormsSynchronizationContext。与 ASP.NET Core 框架的其余部分不同,SignalR 不会使用 ConfigureAwait(false) 等待任务,因为它期望 SynchronizationContext null 在 ASP.NET Core 应用程序中,但是,像 .NET SignalR 客户端这样的客户端库(可以在 ASP.NET Core 应用程序之外使用)是此规则的例外。
在您的应用程序中,当 SignalR 在启动期间初始化其保持活动循环时,SynchronizationContext 为非空,导致其卡住并无法发送保持活动。”他们建议将 SynchronizationContext.SetSynchronizationContext(null) 放入 dll 的初始化中,但警告说这会破坏 WinForm 功能。
我通过创建删除所有 WinForms 引用的库版本解决了该问题。