2011-07-01 22 views
6

En el siguiente código, quiero sincronizar el informe de los resultados de una lista de tareas. Esto funciona ahora porque task.Result bloquea hasta que la tarea se complete. Sin embargo, la tarea id = 3 tarda mucho tiempo en completarse y bloquea todas las demás tareas terminadas al informar su estado.Sincronización de tarea sin un subproceso UI

Creo que puedo hacer esto moviendo los informes (Console.Write) a una instrucción .ContinueWith pero no tengo un subproceso de interfaz de usuario así que ¿cómo obtengo un TaskScheduler para sincronizar las tareas .ContinueWith?

Lo que tengo ahora:

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    var tasks = new List<Task<int>>(); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     var t = Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 5000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }); 
     tasks.Add(t); 
    } 

    foreach (var task in tasks) 
    { 
     Console.WriteLine("Completed {0} on {1}", task.Result, Thread.CurrentThread.ManagedThreadId); 
    } 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 

me gustaría pasar a esto o algo similar, pero necesito el Console.Write ("Completado ...") a todo suceda en el mismo subproceso:

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }).ContinueWith(value => 
     { 
      Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId); 
     } 

    /* need syncronization context */); 
    } 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 

- sOLUCIÓN - Después de conseguir algunos comentarios y leer algunas de las soluciones es la solución completa que hace lo que yo quiero. El objetivo aquí es procesar las tareas de larga ejecución de manera tan rápida como sea posible y luego hacer algo con los resultados de cada tarea de a una por vez.

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    var results = new BlockingCollection<int>(); 

    Task.Factory.StartNew(() => 
    { 
     while (!results.IsCompleted) 
     { 
      try 
      { 
       var x = results.Take(); 
       Console.WriteLine("Completed {0} on {1}", x, Thread.CurrentThread.ManagedThreadId); 
      } 
      catch (InvalidOperationException) 
      { 
      } 
     } 
     Console.WriteLine("\r\nNo more items to take."); 
    }); 

    var tasks = new List<Task>(); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     var t = Task.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      results.Add(num); 
     }); 

     tasks.Add(t); 
    } 

    Task.Factory.ContinueWhenAll(tasks.ToArray(), _ => results.CompleteAdding()); 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 
+0

que asumen el hilo de la consola es un sustituto de una interfaz gráfica de usuario (Windows Forms/WPF). Lo cual no es una buena idea, la presencia de un Dispatcher/Messageloop marca una (gran) diferencia. –

+1

De lo contrario, piense en lo que quiere decir con "pasar en el mismo hilo". No se puede hacer eso a menos que ese hilo sea un sondeo. –

+0

Necesito cambiarlo para que solo una de las tareas ContinueWith se ejecute a la vez. No importa si se ejecutan en el mismo hilo o no, pero no puedo tener dos corriendo en paralelo. En la vida real, la parte ContinueWith está escribiendo una gran cantidad de datos en una base de datos. –

Respuesta

1

Vas a tener que crear una tarea de escritor de algún tipo, sin embargo, tenga en cuenta incluso esta tarea se puede reprogramar en otro hilo nativo o logrado! Al usar el programador predeterminado en TPL, no tiene control sobre qué hilo administrado recibe el trabajo.

public class ConcurrentConsole 
{ 
    private static BlockingCollection<string> output 
     = new BlockingCollection<string>(); 

    public static Task CreateWriterTask(CancellationToken token) 
    { 
     return new Task(
      () => 
      { 
       while (!token.IsCancellationRequested) 
       { 
        string nextLine = output.Take(token); 
        Console.WriteLine(nextLine); 
       } 
      }, 
      token); 
    } 

    public static void WriteLine(Func<string> writeLine) 
    { 
     output.Add(writeLine()); 
    } 
} 

Cuando cambié el código para utilizar este recibí el siguiente resultado:

End of Main 
Done 1 on 6 
Completed 1 on 6 
Done 5 on 9 
Completed 5 on 9 
Done 0 on 4 
Completed 0 on 4 
Done 2 on 5 
Completed 2 on 13 
Done 7 on 10 
Completed 7 on 10 
Done 4 on 8 
Completed 4 on 5 
Done 9 on 12 
Completed 9 on 9 
Done 6 on 6 
Completed 6 on 5 
Done 8 on 11 
Completed 8 on 4 
Done 3 on 7 
Completed 3 on 7 

Incluso con su código de envío de () => String.Format("Completed {0} on {1}"...-ConcurrentConsole.WriteLine, asegurando la ManagedThreadId sería recogido en el ConcurrentConsole tarea, aún alteraría el hilo en el que se ejecutó. Aunque con menos variabilidad que las tareas de ejecución.

+0

Esto es realmente peor que mi ciclo de tarea foreach. Esto espera a que se completen todas las tareas antes de informar los resultados de cualquiera de ellas. Lo que estoy buscando es una manera de informar la finalización de la tarea tan pronto como esté hecha y hacer que los informes se sincronicen con un hilo. No quiero que una tarea larga impida a los demás informar. –

+0

@Ryan: He añadido la presentación de informes a una tarea específica, que es el mejor que se puede garantizar en TPL. – user7116

+0

Esto no es exactamente cómo he implementado el sistema, pero la idea de usar un BlockingCollection está en el núcleo de la aplicación. –

0

que sugeriría:

1) Creación de un objeto de bloqueo
2) Crear una lista de cadenas a ser escrito
3) generará un subproceso que se repite, dormir un poco, luego de bloqueo la lista de cadenas, a continuación, si no está vacía, escribiendo todos ellos y vaciar la lista
4) Otros temas a continuación, la lista de bloqueo, añaden su estado, desbloquear y continúan.

object writeListLocker = new object(); 
List<string> linesToWrite = new List<string>(); 

// Main thread loop 
for (; ;) 
{ 
    lock (writerListLocker) 
    { 
     foreach (string nextLine in linesToWrite) 
      Console.WriteLine(nextLine); 
     linesToWrite.Clear(); 
    } 
    Thread.Sleep(500); 
} 

// Reporting threads 
lock (writerListLocker) 
{ 
    linesToWrite.Add("Completed (etc.)"); 
} 
+0

me gustaría seguir utilizando el TPL si poder. No parece que lo anterior permita que el procesamiento se realice en una serie de subprocesos, mientras que la escritura de los resultados se sincroniza en un subproceso. –

+0

Pero eso es exactamente lo que hace. El ciclo principal del hilo hace toda la escritura. Los otros hilos se agregan al objeto linesToWrite en lugar de escribir. –

1

Puede utilizar OrderedTaskScheduler que exista un sólo finalización de la tarea se ejecuta a la vez; sin embargo, se ejecutarán en una cadena de subprocesos (no necesariamente todos en el mismo subproceso).

Si realmente los necesita todos en el mismo hilo (no solo uno a la vez), puede usar ActionThread del Nito.Async library. Proporciona un SynchronizationContext para su código, que se puede recoger por FromCurrentSynchronizationContext.

+0

Esta es la dirección a la que quiero ir. Quería llevar las operaciones ContinueWith a un único hilo para garantizar que solo una de ellas se ejecute a la vez. Sin embargo, no estoy seguro si puedo usar el OrpedTaskScheduler en producción. Me gustaría utilizar FromCurrentSynchronizationContext pero no puedo porque estoy en un entorno de servicio (a.k.a. no hay subproceso de UI) y SyncronizationContext.Current está devolviendo nulo. –

+0

considero tanto '' OrderedTaskScheduler' y ActionThread' sean calidad de la producción. Parece que cualquiera de ellos podría resolver su problema. –

0

Creo que esperas un resultado como el siguiente.

Starting on 8 
Done 1 on 11 
Completed 1 on 9 
Done 5 on 11 
Completed 5 on 9 
Done 0 on 10 
Completed 0 on 9 
Done 2 on 12 
Completed 2 on 9 
Done 7 on 16 
Completed 7 on 9 
Done 4 on 14 
Completed 4 on 9 
Done 9 on 18 
Completed 9 on 9 
Done 6 on 15 
Completed 6 on 9 
Done 8 on 17 
Completed 8 on 9 
Done 3 on 13 
Completed 3 on 9 

Como abajo, he utilizado la StaSynchronizationContext en mi código de the Understanding SynchronizationContext en donde se explica una llamada sincronizada en un único hilo también. Por favor refierase a eso.

Mi fragmento de código es:

static void Main(string[] args) 
{ 
    StaSynchronizationContext context = new StaSynchronizationContext(); 
    StaSynchronizationContext.SetSynchronizationContext(context); 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 
    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }).ContinueWith(
     value => 
     { 
      Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId); 
     } 
     ,TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 
Cuestiones relacionadas