ASP.NET Core SignalR - 浏览器 window 保持 "loading" 120 秒
ASP.NET Core SignalR - browser window remains "loading" for 120 seconds
背景
我正在尝试构建一个 ASP.NET 核心 Web 应用程序,它需要服务器和客户端之间的双向 real-time 通信。为此,我尝试使用 ASP.NET Core port of SignalR。
环境
- Windows 7 64位开发机
- Visual Studio 2015 IDE
- Firefox 49.0.1(测试用浏览器)
Microsoft.NETCore.App
版本 1.0.1
Microsoft.AspNetCore.SignalR.Server
版本 0.1.0-rtm-21431 (server-side SignalR)
jquery.signalR-2.2.1.js
(client-side SignalR)
- Nuget 来源:
- Nuget (https://api.nuget.org/v3/index.json")
- 代码梳理(https://www.myget.org/F/codecomb-rc2/api/v3/index.json)
- 公司内部 Nuget 存储库
问题
当我启动应用程序(VS2015 中的 IIS Express
按钮)时,一个新的 Firefox 选项卡会在 localhost:...
处打开,呈现我的 Views\Home\Index.cshtml
。这工作正常,页面似乎在几秒钟内完全加载,但 "loading" 纺车图标持续约 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?}");
});
}
通讯中心:
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 逻辑移动到另一个视图后,我的 "Index" 视图仍然需要很长时间才能加载。我回溯了我的 VCS 历史记录,直到找到引入问题的提交并追查到问题的根源。我的HomeController
class在构造的时候被注入了一个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 框架)试图在返回 "Index" 视图后不久处理通信管理器 even虽然 HomeController
将其作为其私有字段之一进行维护。通过在 Dispose
的开头添加一个 return
语句(以防止实际处理任何东西),我发现加载问题消失了。
如何防止 ASP.NET 框架(或其他任何框架)自动处置 CommunicationManager
?我只想在网络服务关闭时处理它。
更新 3
我决定我实际上不需要 ICommunicationManager
来实现 ICommunicationService
,在这种情况下我可以放弃处置支持。这意味着我的应用程序现在可以正常工作了。但是,如果能提供有关 ICommunicationManager.Dispose()
为何自动关闭的任何信息,我将不胜感激 - 我自己粗略搜索后找不到任何信息。
您的 CommunicationsManager
正在处理,因为您使用 AddScoped
注册了它。 每个请求创建一次作用域生命周期服务,它们在作用域(即请求)的末尾处理。
背景
我正在尝试构建一个 ASP.NET 核心 Web 应用程序,它需要服务器和客户端之间的双向 real-time 通信。为此,我尝试使用 ASP.NET Core port of SignalR。
环境
- Windows 7 64位开发机
- Visual Studio 2015 IDE
- Firefox 49.0.1(测试用浏览器)
Microsoft.NETCore.App
版本 1.0.1Microsoft.AspNetCore.SignalR.Server
版本 0.1.0-rtm-21431 (server-side SignalR)jquery.signalR-2.2.1.js
(client-side SignalR)- Nuget 来源:
- Nuget (https://api.nuget.org/v3/index.json")
- 代码梳理(https://www.myget.org/F/codecomb-rc2/api/v3/index.json)
- 公司内部 Nuget 存储库
问题
当我启动应用程序(VS2015 中的 IIS Express
按钮)时,一个新的 Firefox 选项卡会在 localhost:...
处打开,呈现我的 Views\Home\Index.cshtml
。这工作正常,页面似乎在几秒钟内完全加载,但 "loading" 纺车图标持续约 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?}");
});
}
通讯中心:
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 逻辑移动到另一个视图后,我的 "Index" 视图仍然需要很长时间才能加载。我回溯了我的 VCS 历史记录,直到找到引入问题的提交并追查到问题的根源。我的HomeController
class在构造的时候被注入了一个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 框架)试图在返回 "Index" 视图后不久处理通信管理器 even虽然 HomeController
将其作为其私有字段之一进行维护。通过在 Dispose
的开头添加一个 return
语句(以防止实际处理任何东西),我发现加载问题消失了。
如何防止 ASP.NET 框架(或其他任何框架)自动处置 CommunicationManager
?我只想在网络服务关闭时处理它。
更新 3
我决定我实际上不需要 ICommunicationManager
来实现 ICommunicationService
,在这种情况下我可以放弃处置支持。这意味着我的应用程序现在可以正常工作了。但是,如果能提供有关 ICommunicationManager.Dispose()
为何自动关闭的任何信息,我将不胜感激 - 我自己粗略搜索后找不到任何信息。
您的 CommunicationsManager
正在处理,因为您使用 AddScoped
注册了它。 每个请求创建一次作用域生命周期服务,它们在作用域(即请求)的末尾处理。