Почему этот код Parallel.ForEach останавливает программу? - PullRequest
17 голосов
/ 03 декабря 2011

Больше вопросов новичка:

Этот код извлекает несколько прокси из списка в главном окне (я не мог понять, как сделать переменные доступными между различными функциями) и проверяет каждую из них (простой httpwebrequest), а затем добавляет их в список с именем completeProxies.

Почему-то, когда я нажимаю кнопку запуска, вся программа зависает. У меня сложилось впечатление, что 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 работать так, чтобы он не зависал во время работы?

Вот весь код.

Ответы [ 6 ]

18 голосов
/ 03 декабря 2011

Вы не должны запускать параллельную обработку в вашем потоке пользовательского интерфейса. См. Пример под заголовком «Избегать выполнения параллельных циклов в потоке пользовательского интерфейса» в этой странице .

Обновление: Или, вы можете просто создать новый поток вручную и начать обработку внутри него, как я вижу, вы сделали. В этом тоже нет ничего плохого.

Кроме того, как указывает Джим Мишель, вы получаете доступ к спискам из нескольких потоков одновременно, поэтому здесь есть условия гонки. Либо замените ConcurrentBag на List, либо заключайте списки внутри оператора lock при каждом обращении к ним.

9 голосов
/ 15 мая 2013

Хороший способ обойти проблемы невозможности записи в поток пользовательского интерфейса при использовании операторов Parallel - это использовать фабрику задач и делегаты, см. Следующий код, я использую его для перебора серии файлов ви обрабатывает их в параллельном цикле 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'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";
        }
    }
}

Надеюсь, это поможет!

3 голосов
/ 03 декабря 2011

Если кому-то интересно, я вроде бы понял, но не уверен, что это хорошее программирование или какой-то способ решить проблему.

Я создал новую тему примерно так:

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

и уберите все параллельное содержимое внутри do_checks ().

Кажется, все хорошо.

2 голосов
/ 03 августа 2017

, если вы хотите использовать параллельный 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

это решит проблему зависания / зависания или зависания

1 голос
/ 03 декабря 2011

Это то, что, я думаю, может происходить в вашей кодовой базе.

Обычный сценарий: вы нажимаете на кнопку.Не используйте цикл Parallel.Foreach.Используйте класс Dispatcher и вставьте код для запуска в отдельном потоке в фоновом режиме.Как только фоновый поток завершит обработку, он вызовет основной поток пользовательского интерфейса для обновления пользовательского интерфейса.В этом сценарии фоновый поток (вызываемый через Dispatcher) знает о главном потоке пользовательского интерфейса, который требуется отозвать.Или просто сказал, что основной поток пользовательского интерфейса имеет свою собственную идентификацию.

Использование цикла Parallel.Foreach: Как только вы запускаете цикл Paralle.Foreach, платформа использует поток пула потоков.Потоки ThreadPool выбираются случайным образом, и исполняемый код никогда не должен делать никаких предположений относительно идентичности выбранного потока.В исходном коде очень возможно, что поток диспетчера, вызванный через цикл Parallel.Foreach, не может определить поток, с которым он связан.Когда вы используете явный поток, тогда он работает нормально, потому что явный поток имеет свою собственную идентичность, на которую может положиться исполняемый код.

В идеале, если ваша основная задача заключается в том, чтобы поддерживать отзывчивость пользовательского интерфейса, сначала вы должны использовать класс Dispatcher, чтобы поместить код в фоновый поток, а затем использовать любую логику, которую вы хотите ускорить в целом.

1 голос
/ 03 декабря 2011

Одна проблема с вашим кодом заключается в том, что вы вызываете FinishedProxies.Add из нескольких потоков одновременно. Это вызовет проблему, потому что List<T> не является потокобезопасным. Вам потребуется защитить его с помощью блокировки или другого примитива синхронизации или использовать одновременную коллекцию.

Является ли это причиной блокировки пользовательского интерфейса, я не знаю. Без дополнительной информации сложно сказать. Если список proxies очень длинный и выполнение checkProxy не займет много времени, все ваши задачи будут поставлены в очередь за этим вызовом Invoke. Это вызовет целую кучу ожидающих обновлений пользовательского интерфейса. Это заблокирует пользовательский интерфейс, потому что поток пользовательского интерфейса занят обслуживанием этих запросов в очереди.

...