15

Más preguntas para usuarios novatos:¿Por qué este código Parallel.ForEach congela el programa?

Este código captura un número de proxies de la lista en la ventana principal (no pude entender cómo hacer que las variables estén disponibles entre diferentes funciones) y hace una comprobación de cada uno (simple httpwebrequest) y luego los agrega a una lista llamada finishedProxies.

Por alguna razón, cuando presiono el botón de inicio, todo el programa cuelga. Tenía la impresión de que Parallel crea hilos separados para cada acción, dejando el hilo de la interfaz de usuario solo para que sea receptivo.

private void start_Click(object sender, RoutedEventArgs e) 
     { 
      // Populate a list of proxies 
      List<string> proxies = new List<string>(); 
      List<string> finishedProxies = new List<string>(); 

      foreach (string proxy in proxiesList.Items) 
      { 
       proxies.Add(proxy); 
      } 

      Parallel.ForEach<string>(proxies, (i) => 
      { 
       string checkResult; 
       checkResult = checkProxy(i); 

       finishedProxies.Add(checkResult); 
       // update ui 
       /* 
       status.Dispatcher.Invoke(
        System.Windows.Threading.DispatcherPriority.Normal, 
        new Action(
        delegate() 
        { 
         status.Content = "hello" + checkResult; 
        } 
       )); */ 
       // update ui finished 


       //Console.WriteLine("[{0}] F({1}) = {2}", Thread.CurrentThread.Name, i, CalculateFibonacciNumber(i)); 
      }); 


     } 

He intentado usar el código que se comenta fuera a realizar cambios en la interfaz de usuario dentro de la Parallel.Foreach y hace que el programa de congelación después de pulsar el botón de inicio. Me funcionó antes, pero usé la clase Thread.

¿Cómo puedo actualizar la UI desde el Parallel.Foreach y cómo hago que Parallel.Foreach funcione para que no se congele mientras está funcionando?

Here's the whole code.

+2

Estás golpeando el hilo de la interfaz de usuario con las solicitudes de invocación, ya no se ocupa de hacer sus tareas habituales. Como pintar la IU. Al menos baje la prioridad a Background. –

+0

@ dsp_099б ¿A qué te refieres con "Me funcionó antes pero utilicé la clase Thread"? – Fulproof

Respuesta

15

No debe iniciar el procesamiento paralelo en su subproceso de interfaz de usuario. Vea el ejemplo bajo el encabezado "Evitar la ejecución de bucles paralelos en el hilo de la interfaz de usuario" en this page.

Actualización: O bien, simplemente puede crear un nuevo manual de hilo e iniciar el procesamiento dentro de eso como veo que lo ha hecho. No hay nada malo con eso también.

Además, como Jim Mischel señala, está accediendo a las listas de varios hilos al mismo tiempo, por lo que existen condiciones de carrera allí. O sustituya ConcurrentBag por List, o envuelva las listas dentro de una declaración lock cada vez que acceda a ellas.

+4

¿Qué quiere decir con "substituir' ConcurrentBag' por 'List'"? ... ¿Realmente quisiste decir "substituir' List' por 'ConcurrentBag'"? – Fulproof

+1

@Fulproof: creo que el inglés correcto es "sustituir A * por * B" == "sustituir B * por * A". – Jon

+1

["substituir una cosa (A) por otra (B)"] (http://dictionary.reverso.net/english-cobuild/to%20substitute%20a%20for%20b) == "substituir A con B" = = (A) "tiene lugar o realiza la función del otro" (B). ¿Puedes dar alguna referencia a tu uso? – Fulproof

1

Uno de los problemas con su código es que usted está llamando FinishedProxies.Add desde varios subprocesos simultáneamente. Eso va a causar un problema porque List<T> no es seguro para subprocesos. Tendrá que protegerlo con un candado o alguna otra primitiva de sincronización, o usar una colección simultánea.

Si eso causa el bloqueo de UI, no sé. Sin más información, es difícil de decir. Si la lista proxies es muy larga y checkProxy no tarda en ejecutarse, todas sus tareas cola detrás de esa llamada Invoke. Eso va a causar un montón de actualizaciones de UI pendientes. Eso bloqueará la IU porque el hilo de UI está ocupado atendiendo esas solicitudes en cola.

+0

¿Puede decirme más acerca de cómo hacer que la lista sea segura para subprocesos? –

+0

Además, cargo aproximadamente 10 proxies cada vez para ejecutar la prueba, por lo que no es demasiada; ¡Agregué una línea DESPUÉS del parallel.foreach que cambia un recuadro de etiqueta a 'checker is complete!' y cuando presiono el botón de verificación, el programa ESPERA hasta que todos los procesos hayan finalizado antes de actualizar el cuadro, así que es como si todos se ejecutan simultáneamente, pero es como si se ejecutaran simultáneamente en el mismo hilo si eso tiene sentido, porque simplemente la httpwebrequest del mismo subproceso ui hará que cuelgue exactamente de la misma manera. –

2

Si alguien tiene curiosidad, lo averigué, pero no estoy seguro de si esa es una buena programación o cualquier forma de tratar el problema.

he creado un nuevo hilo de esta manera:

Thread t = new Thread(do_checks); 
t.Start(); 

y aparta toda la materia en paralelo dentro de do_checks().

Parece que está bien.

1

Esto es lo que creo que podría estar sucediendo en su código base.

Escenario normal: Hace clic en el botón. No use el bucle Parallel.Foreach.Use la clase Dispatcher y presione el código para ejecutar en una secuencia separada en el fondo. Una vez que el hilo de fondo está listo para procesarse, invocará el hilo principal de la IU para actualizar la IU. En este escenario, el hilo de fondo (invocado a través de Dispatcher) sabe sobre el hilo principal de UI, que necesita devolver. O simplemente dijo que el hilo principal de UI tiene su propia identidad.

Usando el bucle Parallel.Foreach: Una vez que invoca el bucle Paralle.Foreach, el marco utiliza el hilo del subproceso. Los hilos de ThreadPool se eligen de forma aleatoria y el código de ejecución nunca debe suponer nada sobre la identidad del hilo elegido. En el código original es muy posible que el hilo del asignador invocado a través del bucle Parallel.Foreach no pueda descifrar el hilo con el que está asociado. Cuando utiliza un hilo explícito, funciona bien porque el hilo explícito tiene su propia identidad, que puede ser invocada por el código de ejecución.

Idealmente, si su principal preocupación es mantener la interfaz de usuario en buen estado, primero debe usar la clase Dispatcher para insertar el código en el hilo de fondo y luego utilizar la lógica que desee para acelerar la ejecución general.

6

Una buena manera de eludir los problemas de no poder escribir en el hilo de la interfaz de usuario al usar declaraciones en paralelo es usar la Fábrica de tareas y delega, vea el siguiente código. Utilizo esto para iterar sobre una serie de archivos un directorio, y los proces en un bucle foreach en paralelo, después de cada archivo se procesa el hilo de interfaz de usuario se señaliza y actualizado:

var files = GetFiles(directoryToScan); 

tokenSource = new CancellationTokenSource(); 
CancellationToken ct = tokenSource.Token; 

Task task = Task.Factory.StartNew(delegate 
{ 
    // Were we already canceled? 
    ct.ThrowIfCancellationRequested(); 

    Parallel.ForEach(files, currentFile => 
    { 
     // Poll on this property if you have to do 
     // other cleanup before throwing. 
     if (ct.IsCancellationRequested) 
     { 
      // Clean up here, then... 
      ct.ThrowIfCancellationRequested(); 
     } 

     ProcessFile(directoryToScan, currentFile, directoryToOutput); 

     // Update calling thread's UI 
     BeginInvoke((Action)(() => 
     { 
      WriteProgress(currentFile); 
     })); 
    }); 
}, tokenSource.Token); // Pass same token to StartNew. 

task.ContinueWith((t) => 
     BeginInvoke((Action)(() => 
     { 
      SignalCompletion(sw); 
     })) 
); 

y los métodos que hacen los cambios de interfaz de usuario reales:

void WriteProgress(string fileName) 
{ 
    progressBar.Visible = true; 
    lblResizeProgressAmount.Visible = true; 
    lblResizeProgress.Visible = true; 

    progressBar.Value += 1; 
    Interlocked.Increment(ref counter); 
    lblResizeProgressAmount.Text = counter.ToString(); 

    ListViewItem lvi = new ListViewItem(fileName); 
    listView1.Items.Add(lvi); 
    listView1.FullRowSelect = true; 
} 

private void SignalCompletion(Stopwatch sw) 
{ 
    sw.Stop(); 

    if (tokenSource.IsCancellationRequested) 
    { 
     InitializeFields(); 
     lblFinished.Visible = true; 
     lblFinished.Text = String.Format("Processing was cancelled after {0}", sw.Elapsed.ToString()); 
    } 
    else 
    { 
     lblFinished.Visible = true; 
     if (counter > 0) 
     { 
      lblFinished.Text = String.Format("Resized {0} images in {1}", counter, sw.Elapsed.ToString()); 
     } 
     else 
     { 
      lblFinished.Text = "Nothing to resize"; 
     } 
    } 
} 

esperanza ¡esto ayuda!

+0

Puede obtener un aumento de rendimiento adicional utilizando 'BeginInvoke' en el subproceso de interfaz de usuario, entonces no tiene que esperar mientras se actualiza, como lo hace actualmente al usar' Invoke'. Por supuesto, esto podría requerir bloqueos dentro de 'WriteProgress' ... –

+0

El hilo de UI actualmente no se está bloqueando, se mantiene totalmente receptivo, ¿así que no estoy seguro de cómo podría ayudar esto? Pero voy a intentarlo para ver la diferencia, el bloqueo de los objetos no es un problema en mi caso. – StevenVL

+0

Ignore mi comentario sobre el bloqueo - Estaba pensando en varios subprocesos ejecutando 'BeginInvoke', pero como todos se invocan en el hilo de la interfaz de usuario, no puede haber ninguna reincorporación. Lo que estaba diciendo es que cada hilo en su 'Parallel.ForEach' tiene que esperar a que se complete' Invoke', lo que ralentizará las cosas. Con 'BeginInvoke', las actualizaciones de la interfaz de usuario se pondrán en cola en el hilo de la interfaz de usuario y se ejecutarán de forma asíncrona. –

0

si desea utilizar foreach paralelo en el control de interfaz gráfica de usuario como el botón haga clic etc continuación poner foreach paralelo en Task.Factory.StartNew como

private void start_Click(object sender, EventArgs e) 
     { 
       await Task.Factory.StartNew(() => 
        Parallel.ForEach(YourArrayList, (ArraySingleValue) => 
        { 

       Console.WriteLine("your background process code goes here for:"+ArraySingleValue); 
        }) 
        ); 
    }//func end 

se resolverá congelar/o pegado problema de bloqueo

Cuestiones relacionadas