Encontré que InvalidOperationException se lanza con la condición de bloqueo de HubProxy _state.
Utilizo la última versión de Microsoft.AspNet.SignalR.Client (2.2.0).
El método de invocación se ejecuta correctamente.
Entonces, el método Invoke puede emitir una InvalidOperationException con "La colección fue modificada; la operación de enumeración puede no ejecutarse".
La propiedad de elemento de HubProxy (indexador) obtiene o establece el campo "_state" con la declaración de bloqueo.
https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Client/Hubs/HubProxy.cs#L27 -L45
El "_state" es IDictionary
https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Client/Hubs/HubProxy.cs#L159 -L172
Por lo tanto, se lanza InvalidOperationException si la propiedad Item se establece durante la ejecución del método Invoke.
Esto se debe a que el diccionario de la variable "hubData" está enumerado en la biblioteca Json.Net.
https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L926
Solo una idea, este código solucionará este problema.
lock(_state)
{
if (_state.Count != 0)
{
hubData.State = _state;
}
var value = _connection.JsonSerializeObject(hubData);
}
¿Por qué desea establecer el estado y llamar a Invoke en paralelo? El estado se envía al servidor cuando llama a Invoke, por lo que estas llamadas deben serializarse; de lo contrario, hay una condición de carrera y es posible que los datos de estado que establezca no se envíen al servidor. Además, el indexador es un método sincrónico, por lo que configurarlo "en paralelo con Invoke" significa que usted crea sus propios subprocesos, en cuyo caso debe garantizar el orden correcto de las llamadas o primero invoca el método Invoke sin esperar y luego establece el estado en cuyo caso probablemente debería reordenar las llamadas.
¿Puede elaborar más sobre su escenario?
Encontré este problema mientras escribía una herramienta de prueba de esfuerzo para SignalR WebServer. Varios subprocesos de trabajo se ejecutan en paralelo y estos subprocesos comparten la misma instancia de HubProxy.
Entiendo que mi caso es un caso especial. Pero HubProxy debería ser una clase segura para subprocesos. En realidad, el archivo _state está bloqueado durante la propiedad del artículo (indexador). El método de invocación debe bloquear el campo _state de la misma manera.
Creo que este es el mismo problema que planteé aquí: https://github.com/SignalR/SignalR/issues/3631
Estoy de acuerdo con tanaka, hay un bloqueo inconsistente alrededor de _state. Ya sea que sea seguro para subprocesos o no, la existencia de bloqueo me sugiere que debería ser seguro para subprocesos.
Casi un año sin comentarios :-(
El OP menciona que las llamadas paralelas al indexador e Invoke exponen este error. Sin embargo, parece que dos llamadas paralelas a Invoke también expondrán el problema, ya que la devolución de llamada establece el estado a través de ese mismo indexador. https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Client/Hubs/HubProxy.cs#L128
La devolución de llamada puede establecer el estado, mientras que la llamada de otro hilo a Invoke está serializando (y por lo tanto enumerando) el estado. Veo este problema en algunas pruebas automatizadas que crean un proxy y llaman a varios métodos de concentrador en paralelo. Es un escenario del mundo real para llamar a métodos de concentrador en paralelo, por lo que este es un error bastante grave en mi opinión.
¿Por qué necesita llamar a métodos de concentrador en paralelo? ¿Por qué no solucionar el problema pero serializar las llamadas al proxy del concentrador?
De hecho, puedo solucionarlo. Sin embargo, como dijeron goldenc y daniel-smith, hay un bloqueo inconsistente.
En particular, veo este problema en mis propias pruebas de carga.
Si deberíamos llamar a hubproxy en serie, sería mejor eliminar todas las declaraciones de bloqueo alrededor de _state
debido al rendimiento.
Si podemos llamar a hubproxy en parrarel, sería mejor agregar declaraciones de bloqueo alrededor de _state
.
De acuerdo, solo me aseguro de que esto no te bloquee
La razón por la que llamamos a las cosas en paralelo es simplemente porque esperamos múltiples resultados de solicitud / respuesta, potencialmente de larga duración, en paralelo. Incluso si serializamos las llamadas a Invoke, habría pensado que no teníamos control sobre el hilo de devolución de llamada, por lo que no estoy seguro de que esto resolvería el problema. Es decir, no pudimos serializar el trabajo de devolución de llamada, que aún puede actualizar el estado mientras se realiza otra llamada serializada a Invoke.
Este problema se ha cerrado como parte de la limpieza de problemas como se describe en https://blogs.msdn.microsoft.com/webdev/2018/09/17/the-future-of-asp-net-signalr/. Si aún tiene este problema, no dude en volver a abrir y comentar para hacérnoslo saber. Todavía estamos interesados en saber de usted, la acumulación se hizo un poco grande y tuvimos que hacer una limpieza masiva para volver a estar al tanto de las cosas. ¡Gracias por sus continuos comentarios!
Comentario más útil
El OP menciona que las llamadas paralelas al indexador e Invoke exponen este error. Sin embargo, parece que dos llamadas paralelas a Invoke también expondrán el problema, ya que la devolución de llamada establece el estado a través de ese mismo indexador. https://github.com/SignalR/SignalR/blob/master/src/Microsoft.AspNet.SignalR.Client/Hubs/HubProxy.cs#L128
La devolución de llamada puede establecer el estado, mientras que la llamada de otro hilo a Invoke está serializando (y por lo tanto enumerando) el estado. Veo este problema en algunas pruebas automatizadas que crean un proxy y llaman a varios métodos de concentrador en paralelo. Es un escenario del mundo real para llamar a métodos de concentrador en paralelo, por lo que este es un error bastante grave en mi opinión.