referencia poner imprimir escalar escala con como .net multithreading asynchronous async-await tcplistener

.net - imprimir - como poner escala 1:50 en autocad



AplicaciĆ³n basada en TcpListener que no escala bien (2)

Tengo una aplicación de servidor ECHO basada en un TCPListener . Acepta clientes, lee los datos y devuelve los mismos datos. Lo he desarrollado usando el enfoque async / XXXAsync , usando los métodos XXXAsync provistos por el framework.

He establecido contadores de rendimiento para medir cuántos mensajes y bytes entran y salen, y cuántos sockets conectados.

TCPClient una aplicación de prueba que inicia 1400 TCPClient asíncrono y envía un mensaje de 1Kb cada 100-500ms. Los clientes tienen un inicio de espera aleatorio entre 10-1000 ms al principio, por lo que no intentan conectar todos al mismo tiempo. Funciono bien, puedo ver en el PerfMonitor los 1400 conectados, enviando mensajes a buen ritmo. Ejecuto la aplicación cliente desde otra computadora. La CPU y el uso de memoria del servidor son muy pequeños, es un Intel Core i7 con 8 Gb de RAM. El cliente parece estar más ocupado, es un i5 con 4Gb de RAM, pero aún no el 25%.

El problema es si inicio otra aplicación cliente. Las conexiones comienzan a fallar en los clientes. No veo un gran aumento en los mensajes por segundo (un aumento del 20% más o menos), pero veo que la cantidad de clientes conectados ronda entre 1900 y 240, en lugar de los 2800 esperados. El rendimiento disminuye un poco, y el gráfico muestra mayores variaciones entre los mensajes máximos y mínimos por segundo que antes.

Aún así, el uso de la CPU no es ni siquiera el 40% y el uso de la memoria es aún pequeño. Intenté aumentar el número o los subprocesos de grupo tanto en el cliente como en el servidor:

ThreadPool.SetMaxThreads(5000, 5000); ThreadPool.SetMinThreads(2000, 2000);

En el servidor, las conexiones se aceptan en un bucle:

while(true) { var client = await _server.AcceptTcpClientAsync(); HandleClientAsync(client); }

La función HandleClientAsync devuelve una Task , pero como ve el bucle no espera el manejo, simplemente continúa aceptando otro cliente. Esa función de manejo es algo como esto:

public async Task HandleClientAsync(TcpClient client) { while(ws.Connected && !_cancellation.IsCancellationRequested) { var msg = await ReadMessageAsync(client); await WriteMessageAsync(client, msg); } }

Esas dos funciones solo leen y escriben la secuencia de forma asincrónica.

He visto que puedo iniciar TCPListener indicando una cantidad de backlog , pero ¿cuál es el valor predeterminado?

¿Por qué podría ser la razón por la cual la aplicación no está escalando hasta que alcanza la CPU máxima?

¿Cuál sería el enfoque y las herramientas para descubrir cuál es el problema real?

ACTUALIZAR

He intentado la Task.Yield El Task.Yield y la Task.Run acerca la Task.Run , y no ayudaron.

También sucede con el servidor y el cliente ejecutándose localmente en la misma computadora. Incrementar la cantidad de clientes o mensajes por segundo, en realidad reduce el rendimiento del servicio. 600 clientes que envían un mensaje cada 100 ms, generan más rendimiento que 1000 clientes que envían un mensaje cada 100 ms.

Las excepciones que veo en el cliente cuando conecto más de ~ 2000 clientes son dos. Con alrededor de 1500 veo las excepciones al principio, pero los clientes finalmente se conectan. Con más de 1500 veo mucha conexión / desconexión:

"Una conexión existente fue cerrada a la fuerza por el host remoto" (System.Net.Sockets.SocketException) Se detectó una excepción System.Net.Sockets.SocketException: "Una conexión existente fue cerrada a la fuerza por el host remoto"

"No se puede escribir datos en la conexión de transporte: el host remoto ha cerrado a la fuerza una conexión existente". (System.IO.IOException) Se lanzó una excepción System.IO.IOException: "No se pudieron escribir datos en la conexión de transporte: el host remoto cerró forzosamente una conexión existente".

ACTUALIZACIÓN 2

He configurado un proyecto muy simple con servidor y cliente utilizando async / await y escala según lo esperado.

El proyecto donde tengo el problema de escalabilidad es este servidor WebSocket , e incluso cuando utiliza el mismo enfoque, aparentemente algo está causando contención. Hay una aplicación de consola que aloja el componente y una aplicación de consola para generar carga (aunque requiere al menos Windows 8).

Tenga en cuenta que no estoy pidiendo la respuesta para solucionar el problema directamente, sino las técnicas o los enfoques para descubrir qué está causando esa disputa.


Bueno, por un lado, está ejecutando todo en un hilo, por lo que cambiar el ThreadPool no va a hacer ninguna diferencia.

EDITAR : Como señaló Noseration, esto no es realmente cierto. Si bien IOCP y el propio socket asíncrono en realidad no requieren subprocesos adicionales para las solicitudes de E / S, la implementación predeterminada en .NET sí lo requiere. El evento de finalización se procesa en un subproceso de ThreadPool , y es su responsabilidad proporcionar su propio TaskScheduler , o TaskScheduler cola el evento y procesarlo manualmente en un hilo de consumidor. Voy a dejar el resto de la respuesta, porque sigue siendo relevante (y el cambio de hilo no es un problema de rendimiento aquí, como se describe más adelante en la respuesta). También tenga en cuenta que el TaskScheduler defecto en una aplicación de interfaz de usuario por lo general utiliza un contexto de sincronización, por lo que, por ejemplo. winforms, el evento de finalización se procesará en el hilo de UI. En cualquier caso, lanzar más hilos que núcleos de CPU sobre el problema no va a ayudar .

Sin embargo, esto no es necesariamente algo malo. Las operaciones de E / S no se benefician de que se ejecuten en un hilo separado, de hecho, es muy ineficiente hacerlo. Para eso están async y IOCP exactamente, así que sigue usándolo.

Si está empezando a obtener un uso significativo de la CPU, allí es donde desea que las cosas sean paralelas, en lugar de simplemente asincrónicas. Aún así, recibir los mensajes en un hilo usando await debería estar bien. El manejo de multi-threading siempre es complicado, y hay muchos enfoques para diferentes situaciones. En la práctica, generalmente no desea más hilos de los que tiene núcleos de procesador disponibles; si están compitiendo por E / S, use async . Si están compitiendo por CPU, eso solo va a empeorar con más hilos de los que la CPU puede procesar en paralelo.

Tenga en cuenta que dado que se está ejecutando en un subproceso, uno de los núcleos de su procesador podría estar ejecutándose al 100%, mientras que el resto no haría nada. Puede verificar esto en el administrador de tareas fácilmente.

Además, tenga en cuenta que la cantidad de conexiones TCP que puede tener abiertas al mismo tiempo es muy limitada. Cada conexión debe tener sus propios puertos tanto en el cliente como en el servidor. Los valores predeterminados para el cliente de Windows están en algún lugar de la línea de 1000-4000 puertos para eso. Eso no es mucho para un servidor (ni para sus clientes de pruebas de carga).

Si también abre y cierra conexiones, esto empeora aún más, ya que se garantiza que los puertos TCP estarán abiertos durante un tiempo (hasta cuatro minutos después de desconectarse). Esto se debe a que abrir una nueva conexión TCP en el mismo puerto podría significar que los datos de la conexión anterior podrían llegar a la nueva conexión, lo que sería muy, muy malo.

Por favor, agrega más información. ¿Qué hace ReadMessageAsync y WriteMessageAsync ? ¿Es posible que el impacto en el rendimiento sea causado por GC? ¿Has probado perfilando la CPU y la memoria? ¿Estás seguro de que en realidad no estás agotando el ancho de banda de la red con todos esos mensajes TCP? ¿Ha comprobado si está experimentando el agotamiento del puerto TCP o escenarios de pérdida de paquetes altos?

ACTUALIZACIÓN : He escrito un servidor y un cliente de prueba, y pueden agotar los puertos TCP disponibles en menos de un segundo, incluidas todas las inicializaciones, cuando se usan conectores asíncronos. Estoy ejecutando esto en localhost, por lo que cada conexión de cliente realmente toma dos puertos (uno para el servidor, uno para el cliente), por lo que es algo más rápido que cuando el cliente está en una máquina diferente. En cualquier caso, es obvio que el problema en mi caso es el agotamiento del puerto TCP.


Logré escalar hasta 6.000 conexiones simultáneas sin problemas y procesar alrededor de 24,000 mensajes por segundo conectando desde máquina sin máquina (sin prueba de host local) y usando solo alrededor de 80 subprocesos físicos.

Hay algunas lecciones que aprendí:

Aumentar el tamaño del grupo de subprocesos empeoró las cosas

No hagas a menos que sepas lo que estás haciendo.

Llame a Task.Run o ceda con Task.Yield

Para asegurarse de liberar el hilo de llamada, no asista al resto del método.

ConfigureAwait (falso)

Desde su aplicación ejecutable si está seguro de que no se encuentra en un contexto de sincronización con un solo subproceso, esto permite que cualquier subproceso recoja la continuación en lugar de esperar específicamente la que comenzó a ser gratuita.

Byte[]

El generador de perfiles de memoria mostró que la aplicación estaba gastando demasiada memoria y tiempo en la creación de instancias de Byte[] . Así que diseñé varias estrategias para reutilizar las disponibles, o simplemente trabaje "en su lugar" en lugar de crear nuevas y copiar. Los contadores de rendimiento de GC (específicamente "% de tiempo en GC", que era alrededor del 55%) provocaron la alarma de que algo no estaba bien. Además, estaba usando instancias de BitArray para verificar bits en bytes, lo que también causó un poco de sobrecarga de memoria, así que los reemplazo con operaciones de bits y se mejoró. Más tarde descubrí que WCF usa un conjunto Byte[] para hacer frente a este problema.

Asincrónico no significa fast

Asincrónico permite escalar muy bien, pero tiene un costo. El hecho de que haya una operación asincrónica disponible no significa que deba usarla. Use la programación asíncrona cuando suponga que tardará algún tiempo antes de obtener la respuesta real. Si está seguro de que los datos están allí o la respuesta será rápida, proceda de forma sincrónica.

La sincronización y la sincronización de soporte son tediosas

Tienes que implementar los métodos dos veces, no hay una forma a prueba de balas asincrónico desde el código de sincronización.