2011-04-09 34 views
18

Esencialmente, estoy trabajando con esto:¿Es seguro utilizar este hilo de Parallel.ForEach()?

var data = input.AsParallel(); 
List<String> output = new List<String>(); 

Parallel.ForEach<String>(data, line => { 
    String outputLine = ""; 
    // ** Do something with "line" and store result in "outputLine" ** 

    // Additionally, there are some this.Invoke statements for updating UI 

    output.Add(outputLine); 
}); 

de entrada es un objeto List<String>. La instrucción ForEach() realiza algún procesamiento en cada valor, actualiza la interfaz de usuario y agrega el resultado al outputList. ¿Hay algo intrínsecamente incorrecto en esto?

Notas:

  • orden de salida es poco importante

Actualización:

base en la retroalimentación que he recibido, he añadido un manual para lock la instrucción output.Add, así como también el código de actualización de la interfaz de usuario.

+1

¿Cuál es su definición de Thread safe? ¿El orden en materia de salida es importante? Ayudaría si le das todo el código circundante. –

+0

@Sanjeevakumar Hiremath: Lo siento, debería haber incluido esos detalles. –

+2

He editado mi respuesta para mostrar una forma que no implique 'lock' etc. –

Respuesta

26

Sí; List<T> no es seguro para subprocesos, por lo que agregarlo ad-hoc de subprocesos arbitrarios (muy posiblemente al mismo tiempo) está condenado. En su lugar, debe usar una lista de seguridad para subprocesos o agregar el bloqueo manualmente. O tal vez hay un Parallel.ToList.

Además, si es importante: el pedido de inserción no estará garantizado.

Esta versión es segura, sin embargo:

var output = new string[data.Count]; 

Parallel.ForEach<String>(data, (line,state,index) => 
{ 
    String outputLine = index.ToString(); 
    // ** Do something with "line" and store result in "outputLine" ** 

    // Additionally, there are some this.Invoke statements for updating UI 
    output[index] = outputLine; 
}); 

aquí estamos utilizando index para actualizar un índice de matriz diferente por llamada paralelo.

+0

¡Gracias! Afortunadamente, en mi caso, el orden de salida no es importante, así que acabo de envolver la declaración de agregar en un bloqueo. –

+1

No hay un Parallel.ToList pero hay ConcurrentQueue, o ConcurrentStack y otros que se diseñaron al mismo tiempo que el TPL. Son seguros para subprocesos y fáciles de usar. Como el orden no importa, trabajarían aquí. ¡Y sin cerraduras! –

+0

También hay '.AsParallel()' y el método de extensión ParallelEnumerable 'ToList()' –

2

The documentation dice lo siguiente acerca de la seguridad de los subprocesos de List<T>:

estáticos públicos (Shared en Visual Basic) de este tipo son seguros para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos.

Una lista (de T) puede admitir varios lectores al mismo tiempo, siempre que la colección no se modifique. Enumerar a través de una colección no es intrínsecamente un procedimiento seguro para subprocesos. En el raro caso en que una enumeración contenga uno o más accesos de escritura, la única forma de garantizar la seguridad de la secuencia es bloquear la colección durante toda la enumeración. Para permitir que la colección sea accedida por múltiples hilos para lectura y escritura, debe implementar su propia sincronización.

Por lo tanto, es output.Add(outputLine)no flujos seguros y hay que garantizar la seguridad de rosca a sí mismo, por ejemplo, envolviendo la operación de adición en un comunicado lock.

9

¿Hay algo intrínsecamente incorrecto en esto?

Sí, todo. Nada de esto es seguro. Las listas no son seguras para actualizarse en varios subprocesos al mismo tiempo, y no puede actualizar la UI desde ningún subproceso que no sea el subproceso de interfaz de usuario.

+0

Gracias: he agregado bloqueos manuales a ambas cosas. –

+1

@SimpleCoder: Es posible que descubra que pasa más tiempo lidiando con el candado de lo que gana cultivando el trabajo en varios núcleos. ¿El trabajo es realmente caro en comparación con el costo de agregarlo a una lista bloqueada? Si no, probablemente harás que tu programa sea más lento si lo haces en paralelo. Es posible que tenga mejores resultados con una colección diseñada para la sincronización. –

+1

@SimpleCoder: Y * agregar bloqueos * a las actualizaciones de la interfaz de usuario es * incorrecto *. La IU es * apartamento enhebrado *, no * alquiler enhebrado *. La interfaz de usuario solo se puede invocar desde un solo subproceso, el subproceso de interfaz de usuario designado. Poner cerraduras y luego llamarlo desde el hilo equivocado sigue siendo ilegal. Si desea actualizar la interfaz de usuario de un subproceso de trabajo, el subproceso de trabajo debe indicar el subproceso de la interfaz de usuario que necesita el subproceso de la interfaz de usuario para realizar el trabajo. –