Después de indagar en diferentes repositorios y sitios de internet al final nos hemos encontrado la resolución que te enseñaremos a continuación.
Esto es un void-patrón de espera, utilícelo así:
_hub.On("SendMessageToClient", async i => await OnMessageFromServer(i.Id, i.Message))
Sé que esto es antiguo, pero la respuesta aceptada crea una lambda que es async void
.
Pero async void
Los métodos pueden bloquear su aplicación si hay una excepción no controlada. Lea aquí y aquí.
Esos artículos dicen que async void
esta permitido solo debido a eventos, y estos son eventos de los que estamos hablando. Pero sigue siendo true que una excepción puede bloquear toda la aplicación. Entonces, si va a hacerlo, asegúrese de tener try
/catch
bloques en cualquier lugar donde se pueda lanzar una excepción.
Pero async void
Los métodos también pueden causar un comportamiento inesperado porque el código que lo llama no espera a que se complete antes de salir y hacer otra cosa.
Recuerde que el beneficio de await
es que ASP.NET puede apagarse y hacer otra cosa y volver al resto del código más tarde. Por lo general, eso es bueno. Pero en este caso específico, puede significar que dos (o más) mensajes entrantes pueden ser procesados al mismo tiempo y es un lanzamiento para cuáles terminan primero (el primero que termina de procesarse puede no ser el primero que llegó ). Aunque eso puede importar o no en su caso.
Puede que sea mejor esperarlo:
_hub.On("SendMessageToClient",
i => OnMessageFromServer(i.Id, i.Message).GetAwaiter().GetResult());
Vea aquí y aquí el beneficio de usar .GetAwaiter().GetResult()
en vez de .Wait()
.
El cliente de SignalR está diseñado para llamar a los métodos del controlador de forma secuencial, sin intercalar. “SingleThreaded”, en otras palabras. Normalmente, puede diseñar el código del cliente signalR basándose en que todos los métodos del controlador se denominan “SingleThreaded”. (Yo uso “SingleThreaded” entre comillas porque … no es de un solo hilo, pero no parece que tengamos un lenguaje para expresar métodos asíncronos llamados secuencialmente sin intercalar de una manera conceptualmente única = hilo)
Sin embargo, el “async-void”El método que se analiza aquí rompe esta suposición de diseño y causa el efecto secundario inesperado de que los métodos del controlador de cliente ahora se llaman simultáneamente. Aquí está el ejemplo de código que causa el efecto secundario:
/// Yes this looks like a correct async method handler but the compiler is
/// matching the connection.On(string methodName, Action method)
/// overload and we get the "async-void" behaviour discussed above
connection.On(nameof(AsyncHandler), async (i) => await AsyncHandler(i)));
/// This method runs interleaved, "multi-threaded" since the SignalR client is just
/// "fire and forgetting" it.
async Task AsyncHandler(int value)
Console.WriteLine($"Async Starting value");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine($"Async Ending value");
/* Example output:
Async Starting 0
Async Starting 1
Async Starting 2
Async Starting 3
Async Starting 4
Async Starting 5
Async Starting 6
Async Starting 7
Async Starting 8
Async Ending 2
Async Ending 3
Async Ending 0
Async Ending 1
Async Ending 8
Async Ending 7
*/
Si está utilizando ASP.NET Core, podemos adjuntar controladores de métodos asincrónicos y hacer que el cliente los llame uno a la vez, secuencialmente, sin intercalar, sin bloquear ningún hilo. Utilizamos la siguiente invalidación introducida en SignalR para ASP.NET Core.
IDisposable On(this HubConnection hubConnection, string methodName, Type[] parameterTypes,
Func
Aquí está el código que lo logra. Lamentablemente, el código que escribe para adjuntar el controlador es un poco obtuso, pero aquí está:
/// Properly attaching an async method handler
connection.On(nameof(AsyncHandler), new[] typeof(int) , AsyncHandler);
/// Now the client waits for one handler to finish before calling the next.
/// We are back to the expected behaviour of having the client call the handlers
/// one at a time, waiting for each to finish before starting the next.
async Task AsyncHandler(object[] values)
var value = values[0];
Console.WriteLine($"Async Starting value");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine($"Async Ending value");
/* Example output
Async Starting 0
Async Ending 0
Async Starting 1
Async Ending 1
Async Starting 2
Async Ending 2
Async Starting 3
Async Ending 3
Async Starting 4
Async Ending 4
Async Starting 5
Async Ending 5
Async Starting 6
Async Ending 6
Async Starting 7
Async Ending 7
*/
Por supuesto, ahora sabe cómo lograr cualquier tipo de comportamiento del cliente en función de sus requisitos. Si elige usar el async-void comportamiento, sería mejor comentar esto realmente muy bien para no atrapar a otros programadores y asegurarse de no lanzar excepciones de tareas no controladas.
Si conservas algún recelo y disposición de acrecentar nuestro escrito te mencionamos realizar una explicación y con mucho gusto lo analizaremos.