更多新手问题:
这段代码从主窗口的列表中抓取了许多代理(我无法弄清楚如何在不同的函数之间使变量可用)并检查每个代理(简单的httpwebrequest),然后将它们添加到一个名为finishedProxies的列表。
出于某种原因,当我按下开始按钮时,整个程序挂起。我的印象是Parallel为每个动作创建了单独的线程,只留下UI线程以便它响应?
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));
});
}
我已经尝试使用已注释掉的代码来更改Parallel.Foreach中的UI,并且在按下启动按钮后会使程序冻结。它之前对我有用,但我使用了Thread类。
如何从Parallel.Foreach内部更新UI,如何使Parallel.Foreach工作,以便在工作时不会使UI冻结?
答案 0 :(得分:18)
您不能在UI线程中启动并行处理。请参阅this page中“避免在UI线程上执行并行循环”标题下的示例。
更新:或者,你可以简单地创建一个新的线程手册并开始内部处理,就像我看到的那样。这也没有错。
另外,正如Jim Mischel指出的那样,您正在同时访问多个线程的列表,因此存在竞争条件。将ConcurrentBag
替换为List
,或者在每次访问时将列表包装在lock
语句中。
答案 1 :(得分:9)
避免在使用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);
}))
);
执行实际用户界面的方法发生了变化:
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";
}
}
}
希望这有帮助!
答案 2 :(得分:3)
如果有人好奇,我有点想通了,但我不确定这是不是很好的编程或任何方式来处理这个问题。
我创建了一个新的线程:
Thread t = new Thread(do_checks);
t.Start();
并收起do_checks()中的所有并行内容。
似乎没事。
答案 3 :(得分:2)
如果你想在GUI控件中使用 parallel 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
它将解决冻结/卡住或挂起问题
答案 4 :(得分:1)
您的代码的一个问题是您同时从多个线程调用FinishedProxies.Add
。这会导致问题,因为List<T>
不是线程安全的。您需要使用锁或其他同步原语来保护它,或者使用并发集合。
是否导致UI锁定,我不知道。没有更多信息,很难说。如果proxies
列表很长且checkProxy
执行时间不长,那么您的任务将在Invoke
调用后排队。这将导致一大堆未决的UI更新。这将锁定UI,因为UI线程忙于为这些排队的请求提供服务。
答案 5 :(得分:1)
这就是我认为你的代码库可能会发生的事情。
正常情景:您点击按钮。不要使用Parallel.Foreach循环。使用Dispatcher类并推送代码以在后台的单独线程上运行。一旦后台线程完成处理,它将调用主UI线程来更新UI。在这种情况下,后台线程(通过Dispatcher调用)知道它需要回调的主UI线程。或者简单地说主UI线程有自己的身份。
使用Parallel.Foreach循环:一旦调用Paralle.Foreach循环,框架就会使用线程池线程。 ThreadPool线程是随机选择的,执行代码永远不应该对所选线程的身份做出任何假设。在原始代码中,通过Parallel.Foreach循环调用的调度程序线程很可能无法找出与之关联的线程。当您使用显式线程时,它工作正常,因为显式线程有自己的标识,执行代码可以依赖它。
理想情况下,如果你主要关心的是保持UI响应,那么你应该首先使用Dispatcher类在后台线程中推送代码,然后在那里使用你想要的逻辑来加速整体执行。