Método Task.WaitAll vs Parallel.Invoke

Tengo código de ejemplo para comparar el tiempo de procesamiento para el enfoque paralelo y el enfoque de tareas. El objective de este experimento es comprender cómo funcionan.

Así que mis preguntas son:

  1. ¿Por qué Parallel trabajó más rápido que Task?
  2. ¿Mis resultados significan que debo usar Paralelo en lugar de Tarea?
  3. ¿Dónde debo usar Task y donde Parallel?
  4. ¿Qué beneficios de usar Task en comparación con Parallel?
  5. ¿Task es solo un ajuste para el método ThreadPool.QueueUserWorkItem?

    public Task SomeLongOperation() { return Task.Delay(3000); } static void Main(string[] args) { Program p = new Program(); List tasks = new List(); tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation())); tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation())); var arr = tasks.ToArray(); Stopwatch sw = Stopwatch.StartNew(); Task.WaitAll(arr); Console.WriteLine("Task wait all results: " + sw.Elapsed); sw.Stop(); sw = Stopwatch.StartNew(); Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation()); Console.WriteLine("Parallel invoke results: " + sw.Elapsed); sw.Stop(); Console.ReadKey(); } 

Aquí están mis resultados de procesamiento: resultados

EDITAR:

Código cambiado para tener este aspecto:

  Program p = new Program(); Task[] tasks = new Task[2]; Stopwatch sw = Stopwatch.StartNew(); tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation()); tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation()); Task.WaitAll(tasks); Console.WriteLine("Task wait all results: " + sw.Elapsed); sw.Stop(); sw = Stopwatch.StartNew(); Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation()); Console.WriteLine("Parallel invoke results: " + sw.Elapsed); sw.Stop(); 

Mis nuevos resultados:

nuevos resultados

EDIT 2: cuando reemplacé el código con Parallel.Invoke para ser el primero y Task.WaitAll para ser el segundo, la situación ha cambiado cardinalmente. Ahora el paralelo es más lento. Me hace pensar en lo incorrecto de mis estimaciones. Cambié el código para tener este aspecto:

 Program p = new Program(); Task[] tasks = new Task[2]; Stopwatch sw = null; for (int i = 0; i  p.SomeLongOperation(), () => p.SomeLongOperation()); string res = sw.Elapsed.ToString(); Console.WriteLine("Parallel invoke results: " + res); sw.Stop(); } for (int i = 0; i  p.SomeLongOperation()); tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation()); Task.WaitAll(tasks); string res2 = sw.Elapsed.ToString(); Console.WriteLine("Task wait all results: " + res2); sw.Stop(); } 

Y aquí están mis nuevos resultados:

introduzca la descripción de la imagen aquí

introduzca la descripción de la imagen aquí

Ahora puedo sugerir que este experimento es mucho más claro. Los resultados son casi los mismos. A veces paralela y otras veces la tarea es más rápida. Ahora mis preguntas son:

1. ¿Dónde debo usar Task y donde Parallel?

2. ¿Qué beneficios tiene usar Tarea en comparación con Paralelo?

3. ¿Task es solo un ajuste para el método ThreadPool.QueueUserWorkItem?

Cualquier información útil que pueda aclarar esas preguntas son bienvenidas.

EDITAR a partir de este artículo de MSDN :

Tanto Parallel como Task son envoltorios para ThreadPool. La invocación paralela también espera hasta que todas las tareas se terminen.

Relacionado a sus preguntas:

El uso de Task, Parallel o ThreadPool depende de la granularidad de control que necesita tener en la ejecución de sus tareas paralelas. Personalmente, me he acostumbrado a Task.Factory.StartNew() , pero esa es una opinión personal. Lo mismo se relaciona con ThreadPool.QueueUserWorkItem()

Información adicional: la primera llamada a Parallel.Invoke () y Task.Factory.StartNew () puede ser más lenta debido a la inicialización interna.

Si inicia Tareas no genéricas (es decir, “Tareas anuladas sin un valor de retorno”) y las Wait inmediatamente, use Parallel.Invoke lugar. Su intención es inmediatamente clara para el lector.

Use Tareas si:

  • no esperes inmediatamente
  • necesitas valores de retorno
  • Necesitas dar parámetros a los métodos llamados.
  • necesita la funcionalidad TaskCreationOptions
  • necesita la funcionalidad de TaskScheduler o TaskScheduler y no quiere usar ParallelOptions
  • Básicamente, si quieres más opciones o control.

Sí, puedes sortear algunos de estos, por ejemplo, Parallel.Invoke(() => p.OpWithToken(CancellationToken) pero que confunde tu intención. Parallel.Invoke es para hacer un montón de trabajo usando la mayor cantidad de potencia de CPU posible. se hace, no se bloquea, y lo sabes de antemano.


Sin embargo, tu prueba es horrible. La señal de advertencia sería que su acción prolongada es esperar 3000 milisegundos, sin embargo, sus pruebas toman menos de una décima de milisegundo.

 Task.Factory.StartNew(() => p.SomeLongOperation()); 

StartNew realiza una Action y la ejecuta en una nueva Task principal . La acción () => SomeLongOperation() crea una Task subtarea . Después de crear esta subtarea (no completada), se SomeLongOperation() la llamada a SomeLongOperation() y se realiza la acción . Por lo tanto, la Task principal ya se completó después de un décimo milisegundo, mientras que las dos subtareas a las que no tiene referencia todavía se están ejecutando en segundo plano. La ruta paralela también crea dos subtareas, que no rastrea en absoluto, y regresa.

La forma correcta sería tasks[0] = p.SomeLongOperation(); , que asigna una tarea en ejecución a la matriz. Luego, WaitAll verifica el final de esta tarea.