c# - parallel for aggregateexception
¿Por qué este código Parallel.ForEach congela el programa? (6)
Esto es lo que creo que podría estar sucediendo en su base de código.
Escenario normal: haz clic en el botón. No utilice el bucle Parallel.Foreach. Use la clase Dispatcher y presione el código para que se ejecute en un hilo separado en segundo plano. Una vez que se haya procesado el hilo de fondo, invocará el hilo principal de la interfaz de usuario para actualizar la interfaz de usuario. En este escenario, el subproceso en segundo plano (invocado a través de Dispatcher) conoce el subproceso principal de la interfaz de usuario, al que necesita devolverle la llamada. O simplemente dijo que el hilo principal de la interfaz de usuario tiene su propia identidad.
Uso del bucle Parallel.Foreach: una vez que invoque el bucle Paralle.Foreach, el marco utiliza el subproceso de subprocesos. Los subprocesos de ThreadPool se eligen de forma aleatoria y el código que se ejecuta nunca debe suponer la identidad del subproceso elegido. En el código original es muy posible que el subproceso de dispatcher invocado a través del bucle Parallel.Foreach no sea capaz de averiguar el subproceso con el que está asociado. Cuando se utiliza un subproceso explícito, entonces funciona bien porque el subproceso explícito tiene su propia identidad en la que puede confiar el código de ejecución.
Idealmente, si su principal preocupación es mantener la interfaz de usuario receptiva, primero debe usar la clase Dispatcher para empujar el código en el subproceso en segundo plano y luego utilizar la lógica que desee para acelerar la ejecución general.
Más preguntas para principiantes:
Este código toma una serie de proxies de la lista en la ventana principal (no pude averiguar cómo hacer que las variables estén disponibles entre las diferentes funciones) y realiza una comprobación de cada una (httpwebrequest simple) y luego las agrega a una lista llamada acabadosproxies.
Por alguna razón, cuando presiono el botón de inicio, todo el programa cuelga. Tenía la impresión de que Parallel crea subprocesos separados para cada acción, dejando solo el subproceso de la interfaz de usuario para que responda.
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 ha comentado para realizar cambios en la interfaz de usuario dentro de Parallel.Foreach y el programa se congela después de presionar el botón de inicio. Me ha funcionado antes, pero utilicé la clase Thread.
¿Cómo puedo actualizar la IU desde dentro de Parallel.Foreach y cómo puedo hacer que Parallel.Foreach funcione para que no se congele la IU mientras está funcionando?
No debe iniciar el procesamiento paralelo en el subproceso de la interfaz de usuario. Vea el ejemplo en el encabezado "Evitar la ejecución de bucles paralelos en el subproceso de la interfaz de usuario" en esta página .
Actualización: O, simplemente puede crear un nuevo comando manual e iniciar el procesamiento dentro de eso como veo que ha hecho. No hay nada malo con eso también.
Además, como señala Jim Mischel, está accediendo a las listas desde varios hilos al mismo tiempo, por lo que hay condiciones de carrera allí. Reemplace ConcurrentBag
por List
o envuelva las listas dentro de una declaración de lock
cada vez que acceda a ellas.
Si alguien tiene curiosidad, lo resolví un poco, pero no estoy seguro de que sea una buena programación o alguna forma de resolver el problema.
Creé un nuevo hilo así:
Thread t = new Thread(do_checks);
t.Start();
y guarde todas las cosas paralelas dentro de do_checks ().
Parece estar haciendo bien.
Si desea utilizar Foreach paralelo en el control de GUI, como hacer clic en el botón, etc., coloque Forfor paralelo en Task.Factory.StartNew like
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á el problema de congelación / atascado o colgado
Un problema con su código es que está llamando a FinishedProxies.Add
desde varios subprocesos simultáneamente. Eso va a causar un problema porque la List<T>
no es segura para subprocesos. Deberá protegerlo con un bloqueo o alguna otra primitiva de sincronización, o usar una colección concurrente.
Si eso causa el bloqueo de la interfaz de usuario, no lo sé. Sin más información, es difícil de decir. Si la lista de proxies
es muy larga y checkProxy
no tarda mucho en ejecutarse, entonces todas sus tareas se pondrán en cola detrás de esa llamada Invoke
. Eso va a causar un montón de actualizaciones pendientes de la interfaz de usuario. Eso bloqueará la interfaz de usuario porque el subproceso de la interfaz de usuario está ocupado atendiendo esas solicitudes en cola.
Una buena manera de evitar los problemas de no poder escribir en el subproceso de la interfaz de usuario cuando se usan declaraciones paralelas es usar la Fábrica de tareas y los delegados, consulte el siguiente código, uso esto para recorrer una serie de archivos en un directorio procesarlos en un bucle foreach paralelo, después de procesar cada archivo, el subproceso de la interfaz de usuario se señaliza y se actualiza:
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 reales de la interfaz de usuario:
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";
}
}
}
¡Espero que esto ayude!