Tareas asíncronas ‘Obstrucción’

Recientemente comencé a trabajar para tratar de raspar en masa un sitio web con fines de archivo y pensé que sería una buena idea tener múltiples solicitudes web trabajando de forma asíncrona para acelerar las cosas (10,000,000 páginas definitivamente es mucho para archivar) y por eso me aventuré a la dura dueña del paralelismo, tres minutos después, empiezo a preguntarme por qué las tareas que estoy creando (a través de Task.Factory.StartNew ) están ” Task.Factory.StartNew “.

Molesto e intrigado, decidí probar esto para ver si no era solo una circunstancia, así que creé un nuevo proyecto de consola en VS2012 y creé esto:

 static void Main(string[] args) { for (int i = 0; i  { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); Thread.Sleep(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); }); } Console.ReadKey(); } 

Que al correr se le ocurrió este resultado:

Resultados de la prueba

Como puede ver, las primeras cuatro tareas comienzan en una sucesión rápida con tiempos de ~ 0.27, sin embargo, después de eso, las tareas comienzan a boost drásticamente en el tiempo que tardan en comenzar.

¿Por qué sucede esto y qué puedo hacer para solucionar o evitar esta limitación?

Las tareas (de forma predeterminada) se ejecutan en el conjunto de subprocesos, que es tal como suena, un conjunto de subprocesos. El conjunto de hilos está optimizado para muchas situaciones, pero lanzar Thread.Sleep probablemente arroja una llave en la mayoría de ellos. Además, Task.Factory.StartNew generalmente es una mala idea de usar, porque la gente no entiende cómo funciona. Intenta esto en su lugar:

 static void Main(string[] args) { for (int i = 0; i < 10; i++) { int i2 = i + 1; Stopwatch t = new Stopwatch(); t.Start(); Task.Run(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); }); } Console.ReadKey(); } 

Más explicación:

El threadpool tiene un número limitado de hilos a su disposición. Este número cambia dependiendo de ciertas condiciones, sin embargo, en general es cierto. Por esta razón, nunca debe hacer nada bloqueando la agrupación de hilos (si quiere lograr el paralelismo). Thread.Sleep es un ejemplo perfecto de una API de locking, pero también lo es la mayoría de las API de solicitud web, a menos que use las versiones asíncronas más nuevas.

Por lo tanto, el problema en su progtwig original con rastreo es probablemente el mismo que en la muestra que publicó. Está bloqueando todos los subprocesos del grupo de subprocesos y, por lo tanto, se ve obligado a girar nuevos subprocesos y termina por obstruirse.

Extras extras

Casualmente, el uso de Task.Run de esta manera también le permite volver a escribir el código de tal manera que pueda saber cuándo está completo. Al almacenar una referencia a todas las tareas iniciadas y esperarlas todas al final (esto no impide el paralelismo), puede saber de manera confiable cuándo se han completado todas las tareas. A continuación se muestra cómo lograr eso:

 static void Main(string[] args) { var tasks = new List(); for (int i = 0; i < 10; i++) { int i2 = i + 1; Stopwatch t = new Stopwatch(); t.Start(); tasks.Add(Task.Run(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("All tasks completed"); Console.ReadKey(); } 

Nota: este código no ha sido probado

Lee mas

Más información sobre Task.Factory.StartNew y por qué debe evitarse: http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html .

Creo que esto está ocurriendo porque ha agotado todos los subprocesos disponibles en el grupo de subprocesos. Intente iniciar sus tareas utilizando TaskCreationOptions.LongRunning . Más detalles aquí .

Otro problema es que está utilizando Thread.Sleep , esto bloquea el hilo actual y es una pérdida de recursos. Intente esperar de forma asíncrona utilizando await Task.Delay . Puede que necesite cambiar su lambda para ser async .

 Task.Factory.StartNew(async () => { t.Stop(); Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours. Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s"); await Task.Delay(5000); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Task " + i2 + " finished"); });