2010-09-03 33 views
16

Estoy tratando de ejecutar funciones paralelas en una lista de objetos usando la nueva función C# 4.0 Parallel.ForEach. Este es un proceso de mantenimiento muy largo. Me gustaría hacer que se ejecute en el orden de la lista para que pueda detenerme y continuar la ejecución en el punto anterior. ¿Cómo hago esto?Parallel.ForEach Ordered Execution

Aquí hay un ejemplo. Tengo una lista de objetos: a1 to a100. Este es el orden actual:

a1, a51, a2, a52, a3, a53... 

Quiero este orden:

a1, a2, a3, a4... 

estoy bien con algunos objetos que se ejecute fuera de orden, pero siempre que puedo encontrar un punto de la lista, donde Puedo decir que todos los objetos antes de este punto se ejecutaron. Leí el documento técnico de programación en paralelo csharp y no vi nada al respecto. No hay una configuración para esto en la clase ParallelOptions.

Respuesta

1

Como sugerencia alternativa, puede registrar qué objeto se ha ejecutado y luego filtrar la lista cuando reanude la ejecución para excluir los objetos que ya se han ejecutado.

Si esto tiene que ser persistente en todos los reinicios de la aplicación, puede almacenar las ID de los objetos ya ejecutados (supongo que aquí los objetos tienen algún identificador único).

+0

Gracias Chris. Probé con PLINQ y eso es suficiente, pero también mantendré una lista de objetos excluidos. –

2

Si usa Parallel.Break para terminar el ciclo, entonces se le garantiza que todos los índices por debajo del valor devuelto habrán sido ejecutados. Esto es lo más cerca que puedes llegar. El ejemplo aquí usa For pero ForEach tiene sobrecargas similares.

int n = ... 
var result = new double[n]; 

var loopResult = Parallel.For(0, n, (i, loopState) => 
{ 
    if (/* break condition is true */) 
    { 
     loopState.Break(); 
     return; 
    } 
    result[i] = DoWork(i); 
}); 

if (!loopResult.IsCompleted && 
     loopResult.LowestBreakIteration.HasValue) 
{ 
    Console.WriteLine("Loop encountered a break at {0}", 
         loopResult.LowestBreakIteration.Value); 
} 

En un ciclo ForEach, se genera un índice de iteración internamente para cada elemento en cada partición. La ejecución se lleva a cabo fuera de servicio, pero después de la ruptura, usted sabe que todas las iteraciones inferiores a LowestBreakIteration se habrán completado.

Tomado de "Programación en Paralelo con Microsoft .NET" http://parallelpatterns.codeplex.com/

disponible en MSDN. Ver http://msdn.microsoft.com/en-us/library/ff963552.aspx. La sección "Rompiendo bucles temprano" cubre este escenario.

Consulte también: http://msdn.microsoft.com/en-us/library/dd460721.aspx

0

Para cualquier otra persona que viene a través de esta pregunta - si usted está en bucle sobre una matriz o lista (en lugar de un IEnumberable), puede utilizar la sobrecarga de Parallel.Foreach que da la índice de elemento para mantener el orden original también.

string[] MyArray; // array of stuff to do parallel tasks on 
string[] ProcessedArray = new string[MyArray.Length]; 
Parallel.ForEach(MyArray, (ArrayItem,loopstate,ArrayElementIndex) => 
{ 
    string ProcessedArrayItem = TaskToDo(ArrayItem); 
    ProcessedArray[ArrayElementIndex] = ProcessedArrayItem; 
}); 
1

Para cualquiera que busque una solución sencilla, me han publicado 2 métodos de extensión (uno utilizando PLINQ y uno utilizando Parallel.ForEach) como parte de una respuesta a la siguiente pregunta:

Ordered PLINQ ForAll

1

Do algo como esto:

int current = 0; 
object lockCurrent = new object(); 

Parallel.For(0, list.Count, 
      new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, 
      (ii, loopState) => { 
        // So the way Parallel.For works is that it chunks the task list up with each thread getting a chunk to work on... 
        // e.g. [1-1,000], [1,001- 2,000], [2,001-3,000] etc... 
        // We have prioritized our job queue such that more important tasks come first. So we don't want the task list to be 
        // broken up, we want the task list to be run in roughly the same order we started with. So we ignore tha past in 
        // loop variable and just increment our own counter. 
        int thisCurrent = 0; 
        lock (lockCurrent) { 
         thisCurrent = current; 
         current++; 
        } 
        dothework(list[thisCurrent]); 
       }); 

Puede ver cómo cuando salga del paralelo para el ciclo sabrá el último elemento de la lista para ser ejecutado, suponiendo que dejes que todos los hilos terminen antes de romperse. No soy un gran fan de PLINQ o LINQ. Honestamente, no veo cómo escribir LINQ/PLINQ conduce a un código fuente o legibilidad mantenible ... Paralelo.Porque es una solución mucho mejor.

-1

También podría conservar su código y realizar un List.Sort() antes de devolver una respuesta.