微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

关闭/断开ASP.NET Core signalR客户端连接的正确方法是什么?

如何解决关闭/断开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

Blazor服务器页面的完整代码

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 举报,一经查实,本站将立刻删除。