Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

968
Vistas
Retorno asíncrono de llamada de método multicliente de SignalR: .Net Core 6

Estoy tratando de usar signalR para activar procesos y obtener retorno de los clientes en tiempo real. Mi escenario de uso consta de 2 tipos de clientes (Administrador de procesos y Tienda) y un servidor (Hub).

El primer tipo de cliente es el "Administrador de procesos". Este cliente se utilizará para gestionar la ejecución de los procesos. Invocará los métodos del concentrador (servidor) pidiéndole que invoque un determinado método en una determinada conexión. El concentrador luego invocará ese método en esa conexión y "esperará" una respuesta. El segundo tipo de cliente ("Almacenar") ejecutará el método invocado y "regresará" al concentrador invocando un método de respuesta. Ese método concentrador activará la continuación de la tarea que estaba esperando la devolución y luego la respuesta llegará al administrador de procesos al final.

PM -- (invoca)--> HUB -- (invoca)--> STORE -- (invoca)--> HUB -- (retorno)--> PM

Mi problema es que necesito invocar varios métodos de forma asíncrona y, en este momento, la invocación del método de un cliente bloquea las invocaciones posteriores.

Con un poco de investigación, logré escribir el código a continuación. Invoco dos métodos, GetLocalIpAddress y GetMachineName. GetLocalIpAddress tiene un retraso de 5 segundos agregado a propósito para simular si GetMachineName regresaría primero.

Lo que sucedió es que GetMachineName está esperando que GetLocalIpAddress termine antes de regresar.

ingrese la descripción de la imagen aquí

¿Es posible hacer esas llamadas de forma asíncrona? ¿Qué ejecuta primero regresa primero, independientemente de otros métodos? Necesito ese comportamiento para diferentes métodos y para el mismo método invocado varias veces. Por ejemplo, supongamos un método que ejecuta una consulta. Diferentes consultas pueden tardar más que otras en ejecutarse. Quiero devolver los datos tan pronto como se ejecute el método, independientemente de otras invocaciones del mismo método que se enviaron primero y que todavía se están ejecutando.

No tengo mucha experiencia con código asíncrono. Así que, por favor, no dude en señalar o cambiar cualquier cosa incorrecta.

PM Client - Aplicación de consola

 var uri = "https://localhost:7155/SignalR"; HubConnection connection = new HubConnectionBuilder() .WithUrl(uri, (opts) => { opts.Headers.Add("ID", "ProcessManager"); }).Build(); await connection.StartAsync(); HubRequest(0, "Store0", "GetLocalIpAddress"); HubRequest(1, "Store0", "GetMachineName"); Console.ReadLine(); async void HubRequest(int i, string connectionID, string methodName, string? data = null) { Console.WriteLine($"{i} - Request {methodName} from remote client - {connectionID}"); string response = await connection.InvokeAsync<string>("ExecuteOnClient", connectionID, methodName, data); Console.WriteLine($"{i} - Response {methodName} from remote client: {response} - {connectionID}"); }

Servidor - Aplicación web principal de Asp.net

 var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); var app = builder.Build(); app.MapHub<MainHub>("/SignalR"); app.MapGet("/", () => "Hub initialized"); app.Run(); public class MainHub : Hub { static Dictionary<string, string> connectionsList = new Dictionary<string, string>(); private static readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> _responses = new (); public async override Task OnConnectedAsync() { HttpRequest request = Context.GetHttpContext().Request; string id = request.Headers["ID"].ToString().Trim(); string connectionId = Context.ConnectionId; if (!id.Equals(("ProcessManager"))) { id = $"{id}{connectionsList.Where(x => x.Key.Contains("Store")).Count()}"; await Clients.Client(connectionId).SendAsync("AfterConnected", id); } connectionsList.Add(id, connectionId); } public async override Task OnDisconnectedAsync(Exception exception) { string connectionId = Context.ConnectionId; foreach (var item in connectionsList.Where(kvp => kvp.Value == connectionId).ToList()) connectionsList.Remove(item.Key); } public void ClientResponse(Guid guidRequestID, string returnData) { TaskCompletionSource<object> 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("Resposta não Esperada."); //} } public async Task<string> 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<object>(); 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 "TimedOut";//Test } } finally { // Remove the tcs from the dictionary so that we don't leak memory _responses.TryRemove(guidRequestID, out tcs); } } }

Cliente de tienda: aplicación de consola

 string uri = "https://localhost:7155/SignalR"; HubConnection connection = new HubConnectionBuilder() .WithUrl(uri, (opts) => { opts.Headers.Add("ID", "Store"); }).Build(); connection.On<string>("AfterConnected", (data) => { Console.WriteLine($"Client name - {data}"); }); connection.On<string>("GetLocalIpAddress", (guidRequestID) => { Console.WriteLine($"GetLocalIpAddress - {GetLocalIPAddress()}"); Thread.Sleep(5000); connection.InvokeAsync("ClientResponse", guidRequestID, GetLocalIPAddress()); }); connection.On<string>("GetMachineName", (guidRequestID) => { Console.WriteLine($"GetMachineName - {System.Environment.MachineName}"); connection.InvokeAsync("ClientResponse", 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("No network adapters with an IPv4 address in the system!"); }
over 4 years ago · Santiago Trujillo
1 Respuestas
Responde la pregunta

0

Hay dos opciones:

  1. Refactorice los métodos de su concentrador para que, en lugar de esperar un resultado en uno de ellos, almacene información para llamar al cliente cuando llegue un resultado.
  2. Utilice la opción MaximumParallelInvocationsPerClient y configúrela en un valor superior a 1.

El refactor sería algo como lo siguiente:

 private static readonly List<string> _responses = new(); public async Task ExecuteOnClient(string connectionID, string methodName, string data) { _responses.TryAdd(Context.ConnectionID); await Clients.Client(connectionsList[connectionID]).SendAsync(methodName, Context.ConnectionID, data); } public void ClientResponse(string originalConnectionID, string returnData) { if (_responses.TryRemove(originalConnectionID)) { await Clients.Client(originalConnectionID).SendAsync("FromClient", returnData); } }
over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda