title: SignalR multi client method call async return - .Net Core 6
body: <p>I'm trying to use signalR to trigger processes and get return from clients in real-time. My usage cenario consists of 2 client types (Process Manager and Store) and one server(Hub).</p>
  <p>First client type is the &quot;Process Manager&quot;. This client will be used to manage processes execution. It will invoke hub's(server) methods asking it to invoke a certain method on an certain connection. The hub will then invoke that method on that connection and &quot;wait&quot; for an answer. The second client type (&quot;Store&quot;), will execute the invoked method and &quot;return&quot; to the hub by invoking a response method. That hub method will then trigger the continuation of the task that was waiting for the return and then response will hit the process manager in the end.</p>
  <p>PM -- (invokes)--&gt; HUB -- (invokes)--&gt; STORE -- (invokes)--&gt; HUB -- (return)--&gt; PM</p>
  <p>My problem is that I need to invoke multiple methods asynchronously and at this moment, the invoke of a client's method is locking subsequent invocations.</p>
  <p>With some research, I manage to write the code below. I invoke two methods, GetLocalIpAddress and GetMachineName. GetLocalIpAddress has a 5 seconds delay added on purpose to simulate if GetMachineName would return first.</p>
  <p>By what happened is that GetMachineName is waiting for the GetLocalIpAddress to finish before it returns.</p>
  <p><a href="https://i.stack.imgur.com/mXCsZ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/mXCsZ.png" alt="enter image description here" /></a></p>
  <p>Is it possible to make those calls asynchronously ? What executes first returns first, regardless of other methods ? I need that behavior for different methods and for the same method invoked multiple times. For example, suppose a method that executes a query. Different queries may take longer than others to execute. I want to return data as soon as the method execute regardless of other invokations of the same method that were sent first that are still running.</p>
  <p>I don't have much experience with asynchronous code. So please, don't hesitate to point or change anything wrong.</p>
  <p>PM Client - Console application</p>
  <pre><code>var uri = &quot;https://localhost:7155/SignalR&quot;;
  
  HubConnection connection = new HubConnectionBuilder()
      .WithUrl(uri, (opts) =&gt;
      {
          opts.Headers.Add(&quot;ID&quot;, &quot;ProcessManager&quot;);
      }).Build();
  
  await connection.StartAsync();
  
  HubRequest(0, &quot;Store0&quot;, &quot;GetLocalIpAddress&quot;);
  HubRequest(1, &quot;Store0&quot;, &quot;GetMachineName&quot;);
  
  Console.ReadLine();
  
  async void HubRequest(int i, string connectionID, string methodName, string? data = null)
  {
      Console.WriteLine($&quot;{i} - Request {methodName} from remote client - {connectionID}&quot;);
      string response = await connection.InvokeAsync&lt;string&gt;(&quot;ExecuteOnClient&quot;, connectionID, methodName, data);
      Console.WriteLine($&quot;{i} - Response {methodName} from remote client: {response} - {connectionID}&quot;);
  }
  </code></pre>
  <p>Server - Asp.net Core Web App</p>
  <pre><code>var builder = WebApplication.CreateBuilder(args);
  builder.Services.AddSignalR();
  
  var app = builder.Build();
  app.MapHub&lt;MainHub&gt;(&quot;/SignalR&quot;);
  app.MapGet(&quot;/&quot;, () =&gt; &quot;Hub initialized&quot;);
  app.Run();
  
  public class MainHub : Hub
  {
      static Dictionary&lt;string, string&gt; connectionsList = new Dictionary&lt;string, string&gt;();
      private static readonly ConcurrentDictionary&lt;Guid, TaskCompletionSource&lt;object&gt;&gt; _responses = new ();
  
      public async override Task OnConnectedAsync()
      {
          HttpRequest request = Context.GetHttpContext().Request;
          string id = request.Headers[&quot;ID&quot;].ToString().Trim();
          string connectionId = Context.ConnectionId;
  
          if (!id.Equals((&quot;ProcessManager&quot;)))
          {
              id = $&quot;{id}{connectionsList.Where(x =&gt; x.Key.Contains(&quot;Store&quot;)).Count()}&quot;;
              await Clients.Client(connectionId).SendAsync(&quot;AfterConnected&quot;, id);
          }
  
          connectionsList.Add(id, connectionId);
      }
  
      public async override Task OnDisconnectedAsync(Exception exception)
      {
          string connectionId = Context.ConnectionId;
  
          foreach (var item in connectionsList.Where(kvp =&gt; kvp.Value == connectionId).ToList())
              connectionsList.Remove(item.Key);        
      }
  
      public void ClientResponse(Guid guidRequestID, string returnData)
      {
          TaskCompletionSource&lt;object&gt; tcs;
  
          if (_responses.TryGetValue(guidRequestID, out tcs))
          {
              // Trigger the task continuation
              tcs.TrySetResult(returnData);
          }
          //else
          //{
              // Client response for something that isn't being tracked, might be an error
              //Test Only
              //throw new Exception(&quot;Resposta não Esperada.&quot;);
          //}
      }
  
      public async Task&lt;string&gt; ExecuteOnClient(string connectionID, string methodName, string data)
      {
          // Create an entry in the dictionary that will be used to track the client response
          var tcs = new TaskCompletionSource&lt;object&gt;();
          var guidRequestID = Guid.NewGuid();
  
          _responses.TryAdd(guidRequestID, tcs);
  
          // Call method on the client passing the identifier
          if (!string.IsNullOrWhiteSpace(data))
              Clients.Client(connectionsList[connectionID]).SendAsync(methodName, guidRequestID, data);
          else
              Clients.Client(connectionsList[connectionID]).SendAsync(methodName, guidRequestID);
  
          try
          {
              // Wait for the client to respond
              int timeout = 10000;
              var task = tcs.Task;
              if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
              {
                  return (string)await task;
              }
              else
              {
                  //Cancel this task if the client disconnects (potentially by just adding a timeout)
                  return &quot;TimedOut&quot;;//Test
              }
          }
          finally
          {
              // Remove the tcs from the dictionary so that we don't leak memory
              _responses.TryRemove(guidRequestID, out tcs);
          }
      }
  }
  </code></pre>
  <p>Store Client - Console App</p>
  <pre><code>string uri = &quot;https://localhost:7155/SignalR&quot;;
  
  HubConnection connection = new HubConnectionBuilder()
      .WithUrl(uri, (opts) =&gt;
      {
          opts.Headers.Add(&quot;ID&quot;, &quot;Store&quot;);
      }).Build();
  
  connection.On&lt;string&gt;(&quot;AfterConnected&quot;, (data) =&gt;
  {
      Console.WriteLine($&quot;Client name - {data}&quot;);
  });
  
  connection.On&lt;string&gt;(&quot;GetLocalIpAddress&quot;, (guidRequestID) =&gt;
  {
      Console.WriteLine($&quot;GetLocalIpAddress - {GetLocalIPAddress()}&quot;);
      Thread.Sleep(5000);
      connection.InvokeAsync(&quot;ClientResponse&quot;, guidRequestID, GetLocalIPAddress());
  });
  
  connection.On&lt;string&gt;(&quot;GetMachineName&quot;, (guidRequestID) =&gt;
  {
      Console.WriteLine($&quot;GetMachineName - {System.Environment.MachineName}&quot;);
      connection.InvokeAsync(&quot;ClientResponse&quot;, guidRequestID, System.Environment.MachineName);
  });
  
  await connection.StartAsync();
  Console.ReadKey();
  
  static string GetLocalIPAddress()
  {
      var host = Dns.GetHostEntry(Dns.GetHostName());
      foreach (var ip in host.AddressList)
      {
          if (ip.AddressFamily == AddressFamily.InterNetwork)
          {
              return ip.ToString();
          }
      }
      throw new Exception(&quot;No network adapters with an IPv4 address in the system!&quot;);
  }
  </code></pre>