【问题标题】:ASP.NET Core SignalR - browser window remains "loading" for 120 secondsASP.NET Core SignalR - 浏览器窗口保持“加载”120 秒
【发布时间】:2016-09-26 10:59:42
【问题描述】:

背景

我正在尝试构建一个需要在服务器和客户端之间进行双向实时通信的 ASP.NET Core Web 应用程序。我已经尝试过使用ASP.NET Core port of SignalR

环境

问题

当我启动应用程序(VS2015 中的IIS Express 按钮)时,一个新的 Firefox 选项卡在localhost:... 处打开,呈现我的Views\Home\Index.cshtml。这工作正常,页面似乎在几秒钟内完全加载,但“加载”旋转轮图标持续约 123 秒。

从 Application Insights 中,我可以看到浏览器立即对“Home/Index”和“/signalr/hubs”发出 GET 请求,然后在发出进一步的信号器请求之前暂停 120 秒:

在最后三个事件的大约同时,我看到以下日志出现在 Firefox 的调试器中:

[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: Client subscribed to hub 'communicationshub'.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: Negotiating with '/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22communicationshub%22%7D%5D'.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: longPolling transport starting.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: Opening long polling request to 'http://localhost:61192/signalr/connect?transport=longPolling&clientProtocol=1.5&connectionToken=CfDJ8POyhc9yf1ZCtsJ9aOnflZgBHgcMoU0sLdQxkrNhkMLtIP%2BGWCL%2BPNY5H1RhK%2Fl92vibhhTu1PQxPpkcg%2BhpFwYw%2BNyFcTNplZ2HPBXd4QVZVOlP7QR9eIkuoCIDZMFedKEk7kzC7cXBhoF8838KJEAZnGz%2BqQGlePSxmoM6WVhW&connectionData=%5B%7B%22name%22%3A%22communicationshub%22%7D%5D'.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: Long poll complete.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: LongPolling connected.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: longPolling transport connected. Initiating start request.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: Opening long polling request to 'http://localhost:61192/signalr/poll?transport=longPolling&clientProtocol=1.5&connectionToken=CfDJ8POyhc9yf1ZCtsJ9aOnflZgBHgcMoU0sLdQxkrNhkMLtIP%2BGWCL%2BPNY5H1RhK%2Fl92vibhhTu1PQxPpkcg%2BhpFwYw%2BNyFcTNplZ2HPBXd4QVZVOlP7QR9eIkuoCIDZMFedKEk7kzC7cXBhoF8838KJEAZnGz%2BqQGlePSxmoM6WVhW&connectionData=%5B%7B%22name%22%3A%22communicationshub%22%7D%5D'.jquery.signalR-2.2.1.js:82:17
[11:37:43 GMT+0100 (GMT Standard Time)] SignalR: The start request succeeded. Transitioning to the connected state.jquery.signalR-2.2.1.js:82:17
Connected. connectionId : 9d6591af-f1b1-403f-bc42-c81a1b33b25clocalhost:61192:79:21
[11:39:34 GMT+0100 (GMT Standard Time)] SignalR: Long poll complete.jquery.signalR-2.2.1.js:82:17
[11:39:34 GMT+0100 (GMT Standard Time)] SignalR: Opening long polling request to 'http://localhost:61192/signalr/poll?transport=longPolling&clientProtocol=1.5&connectionToken=CfDJ8POyhc9yf1ZCtsJ9aOnflZgBHgcMoU0sLdQxkrNhkMLtIP%2BGWCL%2BPNY5H1RhK%2Fl92vibhhTu1PQxPpkcg%2BhpFwYw%2BNyFcTNplZ2HPBXd4QVZVOlP7QR9eIkuoCIDZMFedKEk7kzC7cXBhoF8838KJEAZnGz%2BqQGlePSxmoM6WVhW&connectionData=%5B%7B%22name%22%3A%22communicationshub%22%7D%5D'.

我的问题是:

  • 为什么建立连接需要这么长时间?
  • 我怎样才能消除这种延迟,以便所有这一切立即发生,而不是在 123 秒后发生?

更多详情

启动 - 配置服务:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddSignalR(options =>
    {
        options.Hubs.EnableDetailedErrors = true;
    });

    services.AddSingleton<CommunicationsHub>();
    services.AddScoped<ICommunicationsManager, CommunicationsManager>();
}

启动 - 配置:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseApplicationInsightsRequestTelemetry();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseApplicationInsightsExceptionTelemetry();

    app.UseStaticFiles();
    app.UseWebSockets();
    app.UseSignalR();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

CommunicationsHub:

public class CommunicationsHub : Hub
{
    public class TransmissionRequestedEventArgs : EventArgs
    {
        public TransmissionRequestedEventArgs(GenericMessage message)
        {
            this.Message = message;
        }

        public GenericMessage Message { get; }
    }

    private readonly IHubContext context;

    public CommunicationsHub(IConnectionManager connectionManager)
    {
        this.context = connectionManager.GetHubContext<CommunicationsHub>();
    }

    public event EventHandler<TransmissionRequestedEventArgs> MessageTransmissionRequested;

    public void OnMessageReceived(GenericMessage message)
    {
        context.Clients.All.onMessageReceived(message);
    }

    public void SendMessage(GenericMessage message)
    {
        MessageTransmissionRequested?.Invoke(this, new TransmissionRequestedEventArgs(message));
        context.Clients.All.onMessageSent(message);
    }
}

Index.cshtml:

@using <<Company>>.Communications.Core
@model IEnumerable<ICommunicationService>
@{
    ViewData["Title"] = "Home Page";
}

@section scripts
{
    <script src="../Scripts/jquery.signalR-2.2.1.js"></script>
    <script src="../signalr/hubs"></script>
    <script>
        $(function () {
            var $tbl = $("#data");
            var comms = $.connection.communicationsHub;
            comms.client.onMessageReceived = function (message) {
                console.log("Message received: " + message)
            };
            comms.client.onMessageSent = function (message) {
                console.log("Message sent: " + message)
            };

            $.connection.hub.logging = true;
            console.log($.connection.hub.logging);
            $.connection.hub.start({ transport: 'longPolling' })
                .done(function () {
                    console.log('Connected. connectionId : ' + $.connection.hub.id);
                })
                .fail(function () {
                    console.log('Could not connect!');
                });
        });
    </script>
}

Please select a communications service to test:
<ul class="nav nav-pills nav-stacked">
    @foreach (var controller in Model)
    {
        <li role="presentation">
            <span class="glyphicon glyphicon-star" aria-hidden="true"></span>@controller
        </li>
    }
</ul>

<table id="data"></table>

更新 1

事实证明这与 SignalR 无关——我的错。

奇怪的是,我发现即使将所有 SignalR 逻辑移动到另一个视图后,我的“索引”视图仍然需要很长时间才能加载。我回顾了我的 VCS 历史,直到找到引入问题的提交并追踪问题的根源。我的HomeController 类在构造过程中注入了ICommunicationManager 的实例,这将导致我的CommunicationManager 被实例化。这是CommunicationManager的构造函数:

public CommunicationsManager()
{
    var assemblies = from name in DependencyContext.Default.GetDefaultAssemblyNames()
                     where name.Name.StartsWith("<<Company Name>>")
                     select Assembly.Load(name);

    var configuration = new ContainerConfiguration()
        .WithAssemblies(assemblies);

    var container = configuration.CreateContainer();
    this.Candidates = container.GetExports<ICommunicationService>();
}

如果我摆脱这种逻辑并将this.Candidates 设置为一个空列表,则页面加载良好,无需 120 多秒的旋转轮。奇怪的是,代码本身执行得非常快,并且页面加载了所有预期的导出。

我会试着弄清楚为什么会发生这种情况,然后我会关闭这个问题。任何帮助仍将不胜感激。

更新 2

我现在对问题的根源有了更好的理解。 ICommunicationManager(间接)继承IDisposable。我在CommunicationManager中实现了处置模式:

#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

protected virtual void Dispose(bool disposing)
{
    if (!disposedValue)
    {
        if (disposing)
        {
            this.Candidates.ToList().ForEach(it => it.Dispose());
        }
        disposedValue = true;
    }
}

// This code added to correctly implement the disposable pattern.
public void Dispose()
{
    // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    Dispose(true);
}
#endregion

通过断点这段代码,我发现某些东西(我假设的 ASP.NET 框架)在返回“索引”视图后不久试图处置通信管理器即使HomeController维护对它的引用作为其私有字段之一。通过在Dispose 的开头添加return 语句(以防止实际处理任何内容),我发现加载问题消失了。

如何防止 ASP.NET 框架(或其他框架)自动释放 CommunicationManager?我只想在 Web 服务关闭时处理它。

更新 3

我已经决定我实际上不需要ICommunicationManager 来实现ICommunicationService,在这种情况下我可以转储处理支持。这意味着我的应用程序现在可以正常工作。但是,我仍然希望了解有关为什么 ICommunicationManager.Dispose() 自动被关闭的任何信息 - 我自己通过粗略的搜索找不到任何东西。

【问题讨论】:

  • 它不应该花那么长时间(尽管我不确定你从哪里得到你的 SignalR 服务器......)。尝试创建一个非常基本的 SiganlR 应用程序,看看它是否可以重现。此外,您的集线器类不需要 ctor。 Clients 应该在没有这个 github.com/aspnet/SignalR-Server/blob/… 的情况下可用
  • 我找到了问题 - 我已经更新了我的帖子。 “另外,你的集线器类上不需要 ctor。没有这个,客户应该可用”你是对的,但我发誓我最初尝试过这个,并且在“请求线程之外的集线器上下文”或某物。我已经按照您的建议进行了更改,但它似乎和以前一样有效。
  • 我认为这是因为您使用 AddScoped 来注册 ICommunicationManager 并且 Scoped 生命周期服务在每个请求中创建一次。 所以它们在作用域的末尾被释放。
  • @Pawel 大概就是这样。我明天会检查这个,如果你是对的,你可以提交它作为答案,我会接受它。
  • @Pawel 通过暂时重新实现 IDisposable,我可以确认您是对的 - AddSingleton 解决了这个问题。如果您愿意,您可以按照“这与 SignalR 无关,而是由 CommunicationManager 的过早处置引起的...”来提交答案。

标签: c# asp.net-core signalr


【解决方案1】:

您的CommunicationsManager 正在被处置,因为您在AddScoped 注册了它。 范围内的生命周期服务为每个请求创建一次,它们在范围(即请求)的末尾被释放。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-20
    • 1970-01-01
    • 2019-02-25
    相关资源
    最近更新 更多