Почему этот код Parallel.ForEach заморозит программу?
Другие вопросы новичков:
Этот код захватывает несколько прокси из списка в главном окне (я не мог понять, как сделать переменные доступными между различными функциями), и выполняет проверку каждого из них (просто httpwebrequest), а затем добавляет их в список под названием finishedProxies.
По какой-то причине, когда я нажимаю кнопку запуска, вся программа зависает. У меня создалось впечатление, что Parallel создает отдельные потоки для каждого действия, оставляя поток пользовательского интерфейса отдельно, чтобы он реагировал?
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, и он запустит программу после нажатия кнопки запуска. Он работал у меня раньше, но я использовал класс Thread.
Как обновить пользовательский интерфейс из Parallel.Foreach и как заставить Parallel.Foreach работать так, чтобы он не заставлял пользовательский интерфейс зависеть во время работы?
Здесь весь код.
Ответы
Ответ 1
Вы не должны запускать параллельную обработку в потоке пользовательского интерфейса. См. Пример в заголовке "Избегайте выполнения параллельных циклов в заголовке пользовательского интерфейса" в этой странице.
Обновление:. Или вы можете просто создать новый манифест потока и начать обработку внутри этого, как я вижу, вы сделали. В этом нет ничего плохого.
Кроме того, как указывает Джим Мишель, вы одновременно получаете доступ к спискам из нескольких потоков, поэтому там есть условия гонки. Либо замените ConcurrentBag
на List
, либо оберните списки внутри оператора lock
каждый раз, когда вы обращаетесь к ним.
Ответ 2
Хороший способ обойти проблемы неспособности писать в поток пользовательского интерфейса при использовании инструкций Parallel - использовать Task Factory и делегаты, см. следующий код, я использую это для итерации по ряду файлов в каталоге и обрабатывает их в параллельном цикле foreach, после обработки каждого файла поток пользовательского интерфейса сигнализируется и обновляется:
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 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";
}
}
}
Надеюсь, это поможет!
Ответ 3
Если кому-то интересно, я как-то понял это, но я не уверен, что это хорошее программирование или любой способ справиться с проблемой.
Я создал новый поток, например:
Thread t = new Thread(do_checks);
t.Start();
и уберите все параллельные вещи внутри do_checks().
Кажется, все в порядке.
Ответ 4
Одна проблема с вашим кодом заключается в том, что вы вызываете FinishedProxies.Add
из нескольких потоков одновременно. Это вызовет проблему, потому что List<T>
не является потокобезопасным. Вам необходимо защитить его с помощью блокировки или другого примитива синхронизации или использовать параллельную коллекцию.
Является ли это причиной блокировки пользовательского интерфейса, я не знаю. Без дополнительной информации это трудно сказать. Если список proxies
очень длинный и checkProxy
не займет много времени, то ваши задачи будут стоять в очереди за этим вызовом Invoke
. Это вызовет целую кучу ожидающих обновлений пользовательского интерфейса. Это заблокирует пользовательский интерфейс, потому что поток пользовательского интерфейса занят обслуживанием этих запрошенных в очереди запросов.
Ответ 5
Это то, что, как я думаю, может происходить в вашей кодовой базе.
Обычный сценарий: вы нажимаете кнопку. Не используйте цикл Parallel.Foreach. Используйте класс Dispatcher и нажмите код для запуска в отдельном потоке в фоновом режиме. После обработки фонового потока он будет вызывать основной поток пользовательского интерфейса для обновления пользовательского интерфейса. В этом случае фоновый поток (вызываемый через Dispatcher) знает о главном потоке пользовательского интерфейса, который ему требуется для обратного вызова. Или просто сказал, что основной поток пользовательского интерфейса имеет свою собственную идентификацию.
Использование цикла Parallel.Foreach: после вызова цикла Paralle.Foreach фреймворк использует поток threadpool. Нити ThreadPool выбираются случайным образом, а исполняемый код никогда не должен делать каких-либо предположений относительно идентичности выбранного потока. В исходном коде очень возможно, что поток диспетчера, вызванный через цикл Parallel.Foreach, не может определить поток, с которым он связан. Когда вы используете явный поток, он работает отлично, потому что явный поток имеет свою собственную идентификацию, на которую может ссылаться исполняемый код.
В идеале, если ваша основная забота заключается в том, чтобы поддерживать пользовательский интерфейс отзывчивым, тогда вы должны сначала использовать класс Dispatcher, чтобы нажимать код в фоновом потоке, а затем использовать то, что когда-либо было логичным для ускорения общего выполнения.
Ответ 6
если вы хотите использовать параллельный foreach в GUI-элементе управления, например, кнопку click и т.д.
затем установите параллельное 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
он разрешит проблему с замораживанием/зависанием или зависанием