I've been struggling with this one for a while. We're using the latest SignalR 2.0.3. The problem happens when we add to multiple SignalR groups. The exception is only thrown when multiple adds happen from the same connectionId with different group names. The exception is only thrown if the LongPolling transport is selected. The exception is only thrown if you add to 6+ unique group names, 5 or less and it works fine.
Here's a simplified example:
Index.cshtml:
@model Int32?
<!DOCTYPE html>
<head>
<title></title>
<script src="@Url.Content("~/Scripts/jquery.min.js")" type="text/javascript"/>
<script src="@Url.Content("~/Scripts/jquery.signalR-2.0.3.min.js")"
type="text/javascript" />
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"/>
</head>
<script>
_testHub = $.connection.testHub;
_testHub.client.sayHello = sayHello;
$.connection.hub.start({ transport: 'longPolling' })
.done(function() {
addAllGroups();
});
function sayHello(aMessage, aGroupName) {
console.info("GroupName: " + aGroupName
+ " Message Sent:" + aMessage);
};
function addAllGroups() {
for (var i = 0;
i < @(Model.HasValue ? Model.Value : 1 ); i++) {
addToGroupAndBroadcast(i);
}
};
function addToGroupAndBroadcast(aGroupName) {
_testHub.server.addToGroupAndBroadcast(aGroupName)
.fail(function (desc) {
console.info("Error: " + desc);
});
};
</script>
SignalRTestController.cs:
using System;
using System.Web.Mvc;
namespace Instrumar.ProductionDashboard.Controllers
{
public class SignalRTestController : Controller
{
#region Public Members
public ActionResult Index()
{
return View((int?)Convert.ToInt32(Request.Url.Segments[4]));
}
#endregion
}
}
TestHub.cs:
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
namespace Instrumar.ProductionDashboard.Hubs
{
public class TestHub : Hub
{
#region Public Members
public void AddToGroupAndBroadcast(string aGroupName)
{
GlobalHost.ConnectionManager.GetHubContext<TestHub>().Groups.Add(Context.ConnectionId, aGroupName).Wait();
Clients.Group(aGroupName).sayHello("Hello", aGroupName);
}
#endregion
}
}
Startup.cs:
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SignalRChat.Startup))]
namespace SignalRChat
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var lHubConfiguration = new HubConfiguration {EnableDetailedErrors = true};
app.UseErrorPage();
app.MapSignalR(lHubConfiguration);
}
}
}
The controller takes an integer as input which is the number of Add's to do. For example, if you call:
http://yourip.com/WebApplicationName/SignalRTest/Index/1 one Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/2 two Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/3 three Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/4 four Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/5 five Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/6 six Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/7 seven Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/8 eight Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/9 nine Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/10 ten Groups.Add broken
...
When I say broken, I get back a "System.Threading.Tasks.TaskCanceledException" in TestHub.cs on the line waiting for the task to complete. Everything works fine with ServerSentEvents but for LongPolling the problem exists. What am I doing wrong? Can I not have more than 5 SignalR groups with LongPolling? Help me! :)
Update: Putting a 1 millisecond sleep using setTimeout between clientside calls fixed the problem. This seems to alleviate the number of pending connections in the network tab. Maybe something happens to the ability to add to a group when you reach the single browser connection limit. It would be nice to know exactly why this doesn't work though.
In the default mode, the app server creates five server connections with Azure SignalR Service. The app server uses the Azure SignalR Service SDK by default. In the following performance test results, server connections are increased to 15 (or more for broadcasting and sending a message to a big group).
SignalR supports full duplex communication that allows the client and server to transfer data back and forth.
SignalR is a Microsoft framework specifically designed to facilitate real-time client/server communication. It provides an effective implementation of long polling that doesn't have a deep impact on the server, and at the same time ensures that clients are appropriately updated about what's going on remotely.
SignalR Hubs are a way to logically group connections as documented. Take an example of a chat application. A group of users could be part of the same hub which allows for sending messages between users in the group. The hubName here can be any string which is used to scope messages being sent between clients.
You can have more than 5 groups with long-polling, but you'll have to work around the connection issue by not adding to groups in a loop.
The task returned from Groups.Add
only completes when it receives an ACK message from the message bus indicating that the message for the client (notifying it of the group add) has been sent.
If there are more than 5 (or whatever number is enforced by the browser) pending AJAX requests tied to the addToGroupAndBroadcast
call from the client, then the message to the client can't be sent. If you attach a done callback to your client call:
_testHub.server.addToGroupAndBroadcast(groupName).done(function() {
console.log("complete");
});
you'll see the first call completes almost immediately.
This is when the first Groups.Add call returns. The message to sayHello
doesn't get sent anymore because it would require another open polling request, which the client is creating, but won't get handled since there are multiple other pending requests from the client that have been enqueued first (and those are all waiting on addToGroupAndBroadcast
to complete).
So after the first call to Groups.Add, the server is unable to send anything to the client, the MessageHub can't send its ACK which would resolve the remaining Groups.Add
promises.
As a result, the TaskCanceledException
gets thrown for those Tasks because of a timeout.
As a work-around, I would suggest to create a hub method which accepts an array of group names:
public async Task AddToGroups(string[] names)
{
foreach (var name in names)
{
await Groups.Add(Context.ConnectionId, name);
Clients.Group(name).sayHello("Hello", name);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With