为什么这个Parallel.ForEach代码会冻结程序?

更多新手问题:

这段代码从主窗口的列表中抓取了许多代理(我无法弄清楚如何在不同的函数之间使变量可用)并检查每个代理(简单的httpwebrequest),然后将它们添加到名为的列表中finishedProxies。

出于某种原因,当我按下开始按钮时,整个程序挂起。 我的印象是Parallel为每个动作创建单独的线程,只留下UI线程,以便它具有响应性?

private void start_Click(object sender, RoutedEventArgs e) { // Populate a list of proxies List proxies = new List(); List finishedProxies = new List(); foreach (string proxy in proxiesList.Items) { proxies.Add(proxy); } Parallel.ForEach(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)); }); } 

我已经尝试使用已注释掉的代码来更改Parallel.Foreach中的UI,它会在按下启动按钮后冻结程序。 它之前对我有用,但我使用的是Thread类。

如何从Parallel.Foreach内部更新UI,如何使Parallel.Foreach工作,以便在工作时不会使UI冻结?

这是整个代码。

您不能在UI线程中启动并行处理。 请参阅此页面中 “避免在UI线程上执行并行循环”标题下的示例。

更新:或者,你可以简单地创建一个新的线程manuall并开始内部处理,就像我看到你做的那样。 这也没什么不对。

另外,正如Jim Mischel指出的那样,您同时从多个线程访问列表,因此存在竞争条件。 将ConcurrentBag替换为List ,或者每次访问时将列表包装在lock语句中。

避免在使用Parallel语句时无法写入UI线程的问题的好方法是使用Task Factory和委托,请参阅以下代码,我使用它来迭代目录中的一系列文件,以及在并行的foreach循环中处理它们,在处理每个文件之后,UI线程被发信号通知并更新:

 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); })) ); 

以及实际UI更改的方法:

 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"; } } } 

希望这可以帮助!

如果有人好奇,我有点想通了,但我不确定这是不错的编程或任何方式来处理这个问题。

我创建了一个新的线程,如下所示:

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

并放弃do_checks()中的所有并行内容。

似乎没事。

您的代码的一个问题是您同时从多个线程调用FinishedProxies.Add 。 这会导致问题,因为List不是线程安全的。 您需要使用锁或其他同步原语来保护它,或使用并发集合。

这是否导致UI锁定,我不知道。 没有更多信息,很难说。 如果proxies列表非常长并且checkProxy执行时间不长,那么您的任务将在Invoke调用之后排队。 这将导致一大堆未决的UI更新。 这将锁定UI,因为UI线程忙于为这些排队的请求提供服务。

这是我认为你的代码库可能会发生的事情。

正常场景:您单击按钮。 不要使用Parallel.Foreach循环。 使用Dispatcher类并推送代码以在后台的单独线程上运行。 一旦后台线程完成处理,它将调用主UI线程来更新UI。 在这种情况下,后台线程(通过Dispatcher调用)知道它需要回调的主UI线程。 或者简单地说主UI线程有自己的标识。

使用Parallel.Foreach循环:一旦调用Paralle.Foreach循环,框架就会使用线程池线程。 ThreadPool线程是随机选择的,执行代码永远不应该对所选线程的身份做出任何假设。 在原始代码中,通过Parallel.Foreach循环调用的调度程序线程很可能无法找出与之关联的线程。 当你使用显式线程时,它工作正常,因为显式线程有自己的身份,执行代码可以依赖它。

理想情况下,如果您的主要关注点是保持UI响应,那么您应该首先使用Dispatcher类在后台线程中推送代码,然后在那里使用您希望加速整体执行的逻辑。

如果你想在GUI控件中使用并行foreach ,如按钮点击等,那么将并行foreach放在Task.Factory.StartNew中

 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 

它将解决冻结/卡住或挂起问题