Подтвердить что ты не робот

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

Здесь весь код.

4b9b3361

Ответ 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

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