2
votes

The ASP.NET SignalR Hubs API Guide - Server (https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server) states the following:

Making a Hub method asynchronous avoids blocking the connection when it uses the WebSocket transport. When a Hub method executes synchronously and the transport is WebSocket, subsequent invocations of methods on the Hub from the same client are blocked until the Hub method completes.

Based on that I assumed when the method is async, method calls on the Hub from the same client are not blocked.

I created a template Angular/ASP.NET Core app (dotnet new angular)(.Net Core 2.2.108), and added the following:

Server:

public class ChatHub : Hub
{
    public async Task<string> GetMessage(string name)
    {
        Console.WriteLine(DateTime.Now + " GetMessage " + name);
        await Task.Delay(3000);
        return "Hello " + name;
    }
}

Client:

export class AppComponent {
  title = 'app';

  private hubConnection: signalR.HubConnection;

  ngOnInit() {

    this.hubConnection = new signalR.HubConnectionBuilder()
      .withUrl('/chatHub')
      .configureLogging(signalR.LogLevel.Debug)
      .build();

    this.hubConnection
      .start()
      .then(() => {
        console.log('Connection started');
        this.logMessage('a');
        this.logMessage('b');
      })
      .catch(err => console.log('Error while starting connection: ' + err));
  }

  private logMessage(name) {
    this.hubConnection.invoke("getMessage", name)
      .then(val => console.log(new Date().toLocaleString() + ": " + val));
    console.log(new Date().toLocaleString() + ": getMessage " + name + " invoked");
  }
}

I got the following output on the client:

[2019-08-08T13:51:09.872Z] Information: Normalizing '/chatHub' to 'https://localhost:44389/chatHub'.
Utils.js:209 [2019-08-08T13:51:09.875Z] Information: Normalizing '/chatHub' to 'https://localhost:44389/chatHub'.
Utils.js:213 [2019-08-08T13:51:09.876Z] Debug: Starting HubConnection.
Utils.js:213 [2019-08-08T13:51:09.878Z] Debug: Starting connection with transfer format 'Text'.
Utils.js:213 [2019-08-08T13:51:09.880Z] Debug: Sending negotiation request: https://localhost:44389/chatHub/negotiate.
core.js:3121 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Utils.js:213 [2019-08-08T13:51:09.930Z] Debug: Selecting transport 'WebSockets'.
Utils.js:209 [2019-08-08T13:51:09.994Z] Information: WebSocket connected to wss://localhost:44389/chatHub?id=mCisfcGtLdYEBMSIix6tvQ.
Utils.js:213 [2019-08-08T13:51:09.995Z] Debug: Sending handshake request.
Utils.js:209 [2019-08-08T13:51:09.996Z] Information: Using HubProtocol 'json'.
Utils.js:213 [2019-08-08T13:51:10.040Z] Debug: Server handshake complete.
app.component.ts:28 Connection started
app.component.ts:38 8/8/2019, 3:51:10 PM: getMessage a invoked
app.component.ts:38 8/8/2019, 3:51:10 PM: getMessage b invoked
app.component.ts:37 8/8/2019, 3:51:13 PM: Hello a
app.component.ts:37 8/8/2019, 3:51:16 PM: Hello b

And on the server (AspNetCore messages stripped):

signalr-test> 2019-08-08 3:51:10 PM GetMessage a
signalr-test> 2019-08-08 3:51:13 PM GetMessage b

There's a 3 sec delay in the second server call and so in the client reply as well. Debugging also shows that the second call is placed on the hub only after the first one finished (the Task completes). It looks like SignalR uses the WebSockets transport, but the server processes the messages sequentially, so the method calls from the same client are blocked, even though the hub method is async.

What do I miss? Or what did I misunderstand?

My experiments:

  • the two GetMessage calls happen on different threads, just the second one starts right after the first one completes
  • messages from other clients are handled immediately (parallel with the first)
  • if I remove the return type (Task GetMessage(string name)), then the same thing happens
  • if I make the method synchronous (string GetMessage(string name)), then the same thing happens, and also the two calls to GetMessage happen on the same thread (as expected, I think)
1
Did you ever find a solution to this? I appear to be having the same problemMcFixit
Not yet, currently I just avoided it. But based on the answer from Sandro below, next time I'll try 2 HubConnection objects.dzs

1 Answers

0
votes

Not a full answer, just an observation. I'm also just learning about working with SignalR and created a method that just sleeps for 10 seconds to simulate long running operations. I read the following docs article to find out about this.

SignalR creates a new instance of your Hub class each time it needs to handle a Hub operation such as when a client connects [..]

So I opened a second tab of the same page. Then I called the hub method once, in both tabs, within 1 second timespan. This was working as I would've expected, the method did not block and delivered both response messages to both tabs inside a 1 second timespan.

I guess it has something to do with the connection. If you make the same call with the same connection it seems to be processed sequentially. But if you use two connections to do the same, it seems to be processed parallel.

It's not a full answer, since the Quote above is not complete, the complete quote reads:

SignalR creates a new instance of your Hub class each time it needs to handle a Hub operation such as when a client connects, disconnects, or makes a method call to the server.

Maybe I just don't understand this correctly, but for the moment my observation is enough for my case. I would still appreciate a full explanation of the behavior, though.