// let's say there is a list of 1000+ URLs
string[] urls = { "http://google.com", "http://yahoo.com", ... };
// now let's send HTTP requests to each of these URLs in parallel
urls.AsParallel().ForAll(async (url) => {
var client = new HttpClient();
var html = await client.GetStringAsync(url);
});
Aquí está el problema, inicia más de 1000 solicitudes web simultáneas. ¿Existe una manera fácil de limitar la cantidad simultánea de estas solicitudes http asincrónicas? Para que no se descarguen más de 20 páginas web en un momento dado. ¿Cómo hacerlo de la manera más eficiente?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Codificador de duelo
fuente
fuente
HttpClient
esIDisposable
, y debes desecharlo, especialmente cuando vas a usar más de 1000 de ellos.HttpClient
se puede utilizar como singleton para múltiples solicitudes.Respuestas:
Definitivamente puede hacer esto en las últimas versiones de async para .NET, usando .NET 4.5 Beta. La publicación anterior de 'usr' apunta a un buen artículo escrito por Stephen Toub, pero la noticia menos anunciada es que el semáforo asíncrono en realidad llegó a la versión Beta de .NET 4.5
Si miras nuestra querida
SemaphoreSlim
clase (que deberías usar ya que es más eficiente que la originalSemaphore
), ahora cuenta con laWaitAsync(...)
serie de sobrecargas, con todos los argumentos esperados: intervalos de tiempo de espera, tokens de cancelación, todos tus amigos de programación habituales: )Stephen también escribió una publicación de blog más reciente sobre las nuevas ventajas de .NET 4.5 que salieron con la versión beta, consulte Novedades del paralelismo en .NET 4.5 Beta .
Por último, aquí hay un código de muestra sobre cómo usar SemaphoreSlim para la limitación del método asíncrono:
Por último, pero probablemente una mención digna de mención, es una solución que utiliza programación basada en TPL. Puede crear tareas vinculadas a delegados en el TPL que aún no se han iniciado y permitir que un programador de tareas personalizado limite la simultaneidad. De hecho, aquí hay una muestra de MSDN:
Consulte también TaskScheduler .
fuente
HttpClient
Parallel.ForEach
funciona con código síncrono. Esto le permite llamar a código asincrónico.IDisposable
mensajesusing
otry-finally
declaraciones y asegurar su eliminación.Si tiene un IEnumerable (es decir, cadenas de URL) y desea realizar una operación de enlace de E / S con cada uno de estos (es decir, realizar una solicitud http asíncrona) al mismo tiempo Y, opcionalmente, también desea establecer el número máximo de Solicitudes de E / S en tiempo real, así es como puede hacerlo. De esta manera, no usa el grupo de subprocesos y otros, el método usa semaphoreslim para controlar el máximo de solicitudes de E / S concurrentes similares a un patrón de ventana deslizante que una solicitud completa, deja el semáforo y la siguiente ingresa.
uso: espera ForEachAsync (urlStrings, YourAsyncFunc, OptionalMaxDegreeOfConcurrency);
fuente
using
sería bueno.Desafortunadamente, .NET Framework carece de los combinadores más importantes para orquestar tareas asíncronas en paralelo. No hay tal cosa incorporada.
Mire la clase AsyncSemaphore construida por el más respetable Stephen Toub. Lo que quieres se llama semáforo y necesitas una versión asíncrona.
fuente
Hay muchas trampas y el uso directo de un semáforo puede ser complicado en casos de error, por lo que sugeriría usar el paquete AsyncEnumerator NuGet en lugar de reinventar la rueda:
fuente
El ejemplo de Theo Yaung es bueno, pero hay una variante sin lista de tareas en espera.
fuente
ProccessUrl
o sus subfunciones será ignorada. Se capturarán en Tareas, pero no se filtrarán al llamador original deCheck(...)
. Personalmente, es por eso que sigo usando Tasks y sus funciones de combinación comoWhenAll
yWhenAny
- para obtener una mejor propagación de errores. :)SemaphoreSlim puede ser muy útil aquí. Aquí está el método de extensión que he creado.
Uso de muestra:
fuente
Antigua pregunta, nueva respuesta. @vitidev tenía un bloque de código que se reutilizó casi intacto en un proyecto que revisé. Después de discutir con algunos colegas, uno preguntó "¿Por qué no usa los métodos TPL integrados?" ActionBlock parece el ganador allí. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Probablemente no terminará cambiando ningún código existente, pero definitivamente buscará adoptar este nuget y reutilizar las mejores prácticas del Sr. Softy para el paralelismo acelerado.
fuente
Aquí hay una solución que aprovecha la naturaleza perezosa de LINQ. Es funcionalmente equivalente a la respuesta aceptada ), pero usa las tareas de los trabajadores en lugar de a
SemaphoreSlim
, reduciendo de esta manera la huella de memoria de toda la operación. Al principio, hagamos que funcione sin estrangulamiento. El primer paso es convertir nuestras URL en una enumeración de tareas.El segundo paso es realizar
await
todas las tareas al mismo tiempo utilizando elTask.WhenAll
método:Salida:
La implementación de Microsoft de
Task.WhenAll
materializa instantáneamente el enumerable proporcionado en una matriz, lo que hace que todas las tareas se inicien a la vez. No queremos eso, porque queremos limitar el número de operaciones asincrónicas concurrentes. Así que necesitaremos implementar una alternativaWhenAll
que enumere nuestro enumerable de manera suave y lenta. Lo haremos creando una cantidad de tareas de trabajo (igual al nivel deseado de simultaneidad), y cada tarea de trabajador enumerará nuestra tarea enumerable una a la vez, usando un candado para asegurar que cada tarea de URL sea procesada por una sola tarea de trabajador. Luego,await
para que se completen todas las tareas de los trabajadores, y finalmente devolvemos los resultados. Aquí está la implementación:... y esto es lo que debemos cambiar en nuestro código inicial, para lograr el estrangulamiento deseado:
Existe una diferencia con respecto al manejo de las excepciones. El nativo
Task.WhenAll
espera a que se completen todas las tareas y agrega todas las excepciones. La implementación anterior finaliza inmediatamente después de completar la primera tarea con errores.fuente
IAsyncEnumerable<T>
se puede encontrar aquí .Aunque es posible que se pongan en cola 1000 tareas muy rápidamente, la biblioteca de Tareas paralelas solo puede manejar tareas simultáneas iguales a la cantidad de núcleos de CPU en la máquina. Eso significa que si tiene una máquina de cuatro núcleos, solo se ejecutarán 4 tareas en un momento dado (a menos que reduzca el MaxDegreeOfParallelism).
fuente
await
palabra clave allí. Eliminar eso debería resolver el problema, ¿correcto?Running
estado) al mismo tiempo que la cantidad de núcleos. Este será especialmente el caso con tareas vinculadas de E / S.Deben utilizarse cálculos en paralelo para acelerar las operaciones vinculadas a la CPU. Aquí estamos hablando de operaciones vinculadas de E / S. Su implementación debe ser puramente asincrónica , a menos que esté abrumando el ocupado núcleo único en su CPU de múltiples núcleos.
EDITAR Me gusta la sugerencia hecha por usr de usar un "semáforo asíncrono" aquí.
fuente
Use
MaxDegreeOfParallelism
, que es una opción que puede especificar enParallel.ForEach()
:fuente
GetStringAsync(url)
está destinado a ser llamado conawait
. Si inspecciona el tipo devar html
, es unTask<string>
, no el resultadostring
.Parallel.ForEach(...)
está diseñado para ejecutar bloques de código síncrono en paralelo (por ejemplo, en diferentes subprocesos).Esencialmente, querrá crear una Acción o Tarea para cada URL que desee ingresar, ponerlas en una Lista y luego procesar esa lista, limitando el número que se puede procesar en paralelo.
Mi entrada de blog muestra cómo hacer esto tanto con Tareas como con Acciones, y proporciona un proyecto de muestra que puede descargar y ejecutar para ver ambos en acción.
Con acciones
Si usa Acciones, puede usar la función incorporada .Net Parallel.Invoke. Aquí lo limitamos a ejecutar como máximo 20 subprocesos en paralelo.
Con tareas
Con Tasks no hay una función incorporada. Sin embargo, puede utilizar el que proporciono en mi blog.
Y luego, creando su lista de tareas y llamando a la función para que se ejecuten, con un máximo de 20 simultáneas a la vez, podría hacer esto:
fuente
esta no es una buena práctica ya que cambia una variable global. tampoco es una solución general para async. pero es fácil para todas las instancias de HttpClient, si eso es todo lo que busca. simplemente puedes probar:
fuente