如何解决关闭/断开ASP.NET Core signalR客户端连接的正确方法是什么?
我是一个新用户,正在努力从ASP.NET Core Blazor服务器页面优雅地关闭辅助signalR客户端。
我正在Blazor Server Page的第一个渲染上建立辅助signalR客户端连接。通过浏览器标签关闭页面时,我正在尝试关闭此辅助signalR客户端连接。
在撰写本文时,通过浏览器选项卡关闭页面似乎未触发disposeAsync
。但是,dispose
方法 IS 被触发。此外,在Safari 13.0.5中,关闭浏览器选项卡时是否不触发dispose
方法?关闭浏览器标签后,Opera,Firefox和Chrome浏览器都会触发dispose
。通过通过macOS Catalina v10.15.7将Safari更新到v14.0(15610.1.28.9,15610)来解决此问题。
当前,我正在从disposeAsync
呼叫dispose
来关闭signalR连接。我正在使用以下代码关闭客户端连接:
...
Logger.Loginformation("Closing secondary signalR connection...");
await hubConnection.StopAsync();
Logger.Loginformation("Closed secondary signalR connection");
...
StopAsync
方法似乎被阻止,即“闭合的辅助信号R连接” 没有消息输出。虽然,我的服务器中心的OndisconnectedAsync
处理程序显示连接已断开。这类似于此behaviour中描述的issue。
如何在 ASP.NET Core 3.1 中正确处理signalR连接?
完整的代码清单如下所示:
处置signalR连接
#region dispose
public void dispose()
{
dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Clear secondary signalR Closed event handler and stop the
/// secondary signalR connection
/// </summary>
/// <remarks>
/// ASP.NET Core Release Candidate 5 calls disposeAsync when
/// navigating away from a Blazor Server page. Until the
/// release is stable disposeAsync will have to be triggered from
/// dispose. Sadly,this means having to use GetAwaiter().GetResult()
/// in dispose().
/// However,providing disposeAsync() Now makes the migration easier
/// https://github.com/dotnet/aspnetcore/issues/26737
/// https://github.com/dotnet/aspnetcore/issues/9960
/// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
/// </remarks>
protected virtual void dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
Logger.Loginformation("Index.razor page is disposing...");
try
{
if (hubConnection != null)
{
Logger.Loginformation("Removing signalR client event handlers...");
hubConnection.Closed -= CloseHandler;
}
// Until ASP.NET Core 5 is released in November
// trigger disposeAsync(). See docstring and DiposeAsync() below.
// not ideal,but having to use GetAwaiter().GetResult() until
// forthcoming release of ASP.NET Core 5 for the introduction
// of triggering disposeAsync on pages that implement IAsyncdisposable
disposeAsync().GetAwaiter().GetResult();
}
catch (Exception exception)
{
Logger.LogError($"Exception encountered while disposing Index.razor page :: {exception.Message}");
}
}
disposed = true;
}
/// <summary>
/// dispose the secondary backend signalR connection
/// </summary>
/// <remarks>
/// ASP.NET Core Release Candidate 5 adds disposeAsync when
/// navigating away from a Blazor Server page. Until the
/// release is stable disposeAsync will have to be triggered from
/// dispose. Sadly,providing disposeAsync() Now makes the migration easier
/// https://github.com/dotnet/aspnetcore/issues/26737
/// https://github.com/dotnet/aspnetcore/issues/9960
/// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
/// </remarks>
public async virtual ValueTask disposeAsync()
{
try
{
if (hubConnection != null)
{
Logger.Loginformation("Closing secondary signalR connection...");
await hubConnection.StopAsync();
Logger.Loginformation("Closed secondary signalR connection");
}
// dispose(); When migrated to ASP.NET Core 5 let disposeAsync trigger dispose
}
catch (Exception exception)
{
Logger.Loginformation($"Exception encountered wwhile stopping secondary signalR connection :: {exception.Message}");
}
}
#endregion
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WebApp.Data;
using WebApp.Data.Serializers.Converters;
using WebApp.Data.Serializers.Converters.Visitors;
using WebApp.Repository.Contracts;
namespace WebApp.Pages
{
public partial class Index : IAsyncdisposable,Idisposable
{
private HubConnection hubConnection;
public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
private bool disposed = false;
[Inject]
public NavigationManager NavigationManager { get; set; }
[Inject]
public IMotionDetectionRepository Repository { get; set; }
[Inject]
public ILogger<MotionDetectionConverter> LoggerMotionDetection { get; set; }
[Inject]
public ILogger<MotionInfoConverter> LoggerMotionInfo { get; set; }
[Inject]
public ILogger<JsonVisitor> LoggerjsonVisitor { get; set; }
[Inject]
public ILogger<Index> Logger { get; set; }
#region dispose
public void dispose()
{
dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Clear secondary signalR Closed event handler and stop the
/// secondary signalR connection
/// </summary>
/// <remarks>
/// ASP.NET Core Release Candidate 5 calls disposeAsync when
/// navigating away from a Blazor Server page. Until the
/// release is stable disposeAsync will have to be triggered from
/// dispose. Sadly,providing disposeAsync() Now makes the migration easier
/// https://github.com/dotnet/aspnetcore/issues/26737
/// https://github.com/dotnet/aspnetcore/issues/9960
/// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
/// </remarks>
public async virtual ValueTask disposeAsync()
{
try
{
if (hubConnection != null)
{
Logger.Loginformation("Closing secondary signalR connection...");
await hubConnection.StopAsync();
Logger.Loginformation("Closed secondary signalR connection");
}
// dispose(); When migrated to ASP.NET Core 5 let disposeAsync trigger dispose
}
catch (Exception exception)
{
Logger.Loginformation($"Exception encountered wwhile stopping secondary signalR connection :: {exception.Message}");
}
}
#endregion
#region ComponentBase
/// <summary>
/// Connect to the secondary signalR hub after rendering.
/// Perform on the first render.
/// </summary>
/// <remarks>
/// This Could have been performed in OnInitializedAsync but
/// that method gets executed twice when server prerendering is used.
/// </remarks>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
try
{
Logger.Loginformation("Index.razor page is performing initial render,connecting to secondary signalR hub");
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.AddFilter("Microsoft.AspNetCore.SignalR",LogLevel.information);
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection,LoggerMotionInfo,LoggerjsonVisitor);
})
.Build();
hubConnection.On<MotionDetection>("ReceiveMotionDetection",ReceiveMessage);
hubConnection.Closed += CloseHandler;
Logger.Loginformation("Starting HubConnection");
await hubConnection.StartAsync();
Logger.Loginformation("Index Razor Page initialised,listening on signalR hub url => " + hubUrl.ToString());
}
catch (Exception e)
{
Logger.LogError(e,"Encountered exception => " + e);
}
}
}
protected override async Task OnInitializedAsync()
{
await Task.CompletedTask;
}
#endregion
#region signalR
/// <summary>Log signalR connection closing</summary>
/// <param name="exception">
/// If an exception occurred while closing then this argument describes the exception
/// If the signaR connection was closed intentionally by client or server,then this
/// argument is null
/// </param>
private Task CloseHandler(Exception exception)
{
if (exception == null)
{
Logger.Loginformation("signalR client connection closed");
}
else
{
Logger.Loginformation($"signalR client closed due to error => {exception.Message}");
}
return Task.CompletedTask;
}
/// <summary>
/// Add motion detection notification to repository
/// </summary>
/// <param name="message">Motion detection received via signalR</param>
private void ReceiveMessage(MotionDetection message)
{
try
{
Logger.Loginformation("Motion detection message received");
Repository.AddItem(message);
StateHasChanged();
}
catch (Exception ex)
{
Logger.LogError(ex,"An exception was encountered => " + ex.ToString());
}
}
#endregion
}
}
signalR服务器中心
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace WebApp.Realtime.SignalR
{
/// <summary>
/// This represents endpoints available on the server,available for the
/// clients to call
/// </summary>
public class MotionHub : Hub<IMotion>
{
private bool _disposed = false;
public ILogger<MotionHub> Logger { get; set; }
public MotionHub(ILogger<MotionHub> logger) : base()
{
Logger = logger;
}
public override async Task OnConnectedAsync()
{
Logger.Loginformation($"OnConnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name}");
await base.OnConnectedAsync();
}
public override async Task OndisconnectedAsync(Exception exception)
{
if (exception != null)
{
Logger.Loginformation($"OndisconnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name} : Exception={exception.Message}");
}
else
{
Logger.Loginformation($"OndisconnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name}");
}
await base.OndisconnectedAsync(exception);
}
// Protected implementation of dispose pattern.
protected override void dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
// Call base class implementation.
base.dispose(disposing);
}
}
}
解决方法
在ASP.NET Core Github Discussions的帮助下得到修复。
在Dispose
方法内替换为DisposeAsync().GetAwaiter().GetResult(); to _ = DisposeAsync();
,这将调用DiposeAsync()
,而无需等待任务结果。
还更新了我的代码以停止集线器连接:
try { await hubConnection.StopAsync(); }
finally
{
await hubConnection.DisposeAsync();
}
在DisposeAsync
的{{1}}内的StopAsync
调用不再阻塞,连接正常关闭。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。