Tareas de continuación suspendidas al utilizar LimitedConcurrencyLevelTaskScheduler
Tengo mi trabajo en el uso de la TPL en C # (. NET 4.0).
He creado una API personalizada para facilitar la creación de solicitudes web y descargar el contenido (asincrónicamente, mediante tareas de continuación). Esa parte está funcionando bien.
- JsonSerializationException 'No se puede encontrar un constructor' en Xamarin.Android
- La aplicación que implementa Parse Unity Plugin se bloquea en el dispositivo android pero funciona bien en el editor
- MonoDroid, interfaz de usuario común de MonoTouch
- Envío de correo electrónico desde inside unity3d
- Leer datos adicionales de intenciones de Android en el lanzamiento de la aplicación de Unity
El problema que tengo se produce cuando intento utilizar el LimitedConcurrencyLevelTaskScheduler
(encontrado en las muestras para la programación paralela y en la documentación de MSDN para las tareas ) con la creación de tarea diferida. Si no está familiarizado con esa clase, todo lo que hace es limitar el grado de concurrencia de las tareas programadas a un número arbitrario.
Básicamente, quiero aplazar la creación de cadenas de tareas de solicitud web en una tarea programada por el LimitedConcurrencyLevelTaskScheduler
para poder limitar el número de descargas simultáneas.
Como sugiere el sabio Stephen Toub , al diferir la creación de una Task
, lo mejor es diseñar su API para devolver Func<Task>
o Func<Task<TResult>>
. He hecho esto.
Lamentablemente, mi programa se bloquea después de programar el primer conjunto de tareas simultáneas. Digamos que tengo mis tareas limitadas a 4 grados de concurrencia. En ese caso, se iniciarían 4 tareas y luego se bloquearía el programa. Las tareas nunca completarían.
He creado un ejemplo mínimo para ilustrar el problema simplemente. Estoy usando archivos lee en lugar de usar un WebRequest
. He limitado los grados de concurrencia a 1.
class Program { static Func<Task> GetReadTask() { return () => { Console.WriteLine("Opening file."); FileStream fileStream = File.Open("C:\\Users\\Joel\\Desktop\\1.txt", FileMode.Open); byte[] buffer = new byte[32]; Console.WriteLine("Beginning read."); return Task<int>.Factory.FromAsync(fileStream.BeginRead, fileStream.EndRead, buffer, 0, 32, null).ContinueWith(task => fileStream.Close()); }; } static void Main() { LimitedConcurrencyLevelTaskScheduler ts = new LimitedConcurrencyLevelTaskScheduler(1); TaskFactory factory = new TaskFactory(ts); int[] range = {1, 2, 3}; var tasks = range.Select(number => { Func<Task> getTask = GetReadTask(); return factory.StartNew(() => { var task = getTask(); task.Wait(); }); }); Task.WaitAll(tasks.ToArray()); } }
Para aclarar lo que quiero decir con "se cuelga", esto es lo que parece el resultado.
Opening file. Beginning read.
Y entonces no se imprime nada más … para siempre.
¿Alguna pista sobre lo que está pasando?
- Prueba de la aplicación Xamarin en un dispositivo Android
- Unity C # scrollable GUI.BOX no funciona en android
- ¿Por qué MonoDroid no puede encontrar mis ensamblajes?
- Xamarin sider.dll falta
- Cómo enviar notificaciones push de Android a través de GCM en C # .Net
- Pasando variables de ViewModel a otro View (MVVMCross)
- System.TypeLoadException: No se pudo resolver el tipo con el token 01000019
- Zxing integración en la aplicación monodroid
¡Buena pregunta!
En primer lugar, no estoy seguro de LimitedConcurrencyLevelTaskScheduler
es la solución académicamente correcta. Para que esto limite el número de solicitudes simultáneas a N, tiene que bloquear N tareas que tipo de derrotas el propósito de utilizar APM async llamadas en el primer lugar.
Dicho esto, es mucho más fácil de implementar que la alternativa. Usted necesitaría tener una cola de trabajo y mantener la cuenta del número de solicitudes de vuelo, y luego crear tareas de trabajo según sea necesario. Eso no es trivial para obtener la derecha y si el número N de peticiones simultáneas será pequeño, tener N hilos bloqueados no es el fin del mundo.
Por lo tanto, el problema con su código es que las tareas creadas dentro de otras tareas utilizan el planificador de la tarea principal. En realidad esto no es cierto para las tareas creadas con FromAsync
ya que éstas usan la implementación de APM subyacente y por lo tanto son un poco diferentes.
Crea tareas en Main
con:
return factory.StartNew( () => { var task = getTask(); task.Wait(); } );
factory
utiliza el LimitedConcurrencyLevelTaskScheduler( 1 )
, por lo que sólo una de estas tareas se puede ejecutar simultáneamente y que uno está esperando en la tarea devuelta por getTask()
.
Por lo tanto, en GetReadTask
llama Task<int>.Factory.FromAsync
. Esto se ejecuta porque FromAsync
no respeta el programador de la tarea primaria.
A continuación, crear una continuación con .ContinueWith(task => fileStream.Close())
. Esto crea una tarea que respeta el planificador de su padre. Dado que LimitedConcurrencyLevelTaskScheduler
ya está ejecutando una tarea (la que está bloqueada en Main
), la continuación no se puede ejecutar y tiene un bloqueo.
La solución es ejecutar la continuación en un subproceso de grupo de subprocesos normal con TaskScheduler.Default
. Entonces se ejecuta y el bloqueo se rompe.
Aquí está mi solución:
static Task QueueReadTask( TaskScheduler ts, int number ) { Output.Write( "QueueReadTask( " + number + " )" ); return Task.Factory.StartNew( () => { Output.Write( "Opening file " + number + "." ); FileStream fileStream = File.Open( "D:\\1KB.txt", FileMode.Open, FileAccess.Read, FileShare.Read ); byte[] buffer = new byte[ 32 ]; var tRead = Task<int>.Factory.FromAsync( fileStream.BeginRead, fileStream.EndRead, buffer, 0, 32, null ); var tClose = tRead.ContinueWith( task => { Output.Write( "Closing file " + number + ". Read " + task.Result + " bytes." ); fileStream.Close(); } , TaskScheduler.Default ); tClose.Wait(); } , CancellationToken.None , TaskCreationOptions.None , ts ); }
Y Main
ahora se ve así:
static void Main() { LimitedConcurrencyLevelTaskScheduler ts = new LimitedConcurrencyLevelTaskScheduler( 1 ); int[] range = { 1, 2, 3 }; var tasks = range.Select( number => { var task = QueueReadTask( ts, number ); return task.ContinueWith( t => Output.Write( "Number " + number + " completed" ) ); } ) .ToArray(); Output.Write( "Waiting for " + tasks.Length + " tasks: " + String.Join( " ", tasks.Select( t => t.Status ).ToArray() ) ); Task.WaitAll( tasks ); Output.Write( "WaitAll complete for " + tasks.Length + " tasks: " + String.Join( " ", tasks.Select( t => t.Status ).ToArray() ) ); }
Hay un par de cosas a tener en cuenta:
Mover la task.Wait()
en QueueReadTask
hace más obvio que está bloqueando una tarea. Puede quitar la llamada FromAsync
y la continuación y reemplazarlos por una llamada síncrona normal, ya que está bloqueando de todos modos.
La tarea devuelta de QueueReadTask
puede tener continuaciones. De forma predeterminada, éstos se ejecutan bajo el programador predeterminado porque heredan el programador de la tarea principal y no el del antecedente. En este caso, no hay tarea padre, por lo que se utiliza el planificador predeterminado.
- Cómo tomar una captura de pantalla de Android (Emulator) utilizando esta biblioteca y dónde puedo obtener las capturas de pantalla
- TextToSpeech.setEngineByPackageName () no establece nada