c # Parallel.For и обновление пользовательского интерфейса? - PullRequest
4 голосов
/ 24 января 2011

Я пытаюсь реализовать цикл Parallel.ForEach, чтобы заменить старый цикл foreach, но у меня возникают проблемы с обновлением пользовательского интерфейса (у меня есть счетчик, показывающий что-то вроде «обработанные файлы x / y»).Я сделал Parallel Forloop пример, чтобы проиллюстрировать мою проблему (метка не обновляется).

using System;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;

namespace FormThreadTest
{
    public partial class Form1 : Form
    {
        private SynchronizationContext m_sync;
        private System.Timers.Timer m_timer;
        private int m_count;

        public Form1()
        {                       
            InitializeComponent();

            m_sync = SynchronizationContext.Current;

            m_count = 0;

            m_timer = new System.Timers.Timer();
            m_timer.Interval = 1000;
            m_timer.AutoReset = true;
            m_timer.Elapsed += new System.Timers.ElapsedEventHandler(m_timer_Elapsed);
            m_timer.Start();
        }

        private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                m_sync.Post((o) =>
                {
                    label1.Text = m_count.ToString();
                    Application.DoEvents();
                }, null);
            });
        }

        private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                });
            });
        }
    }
}

Если я изменю метод события нажатия кнопки и добавлю Thread.Sleep (), он кажетсячтобы дать время потоку обновления пользовательского интерфейса выполнить свою работу:

private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                    Thread.Sleep(10);
                });
            });
        }

Нет ли способа избежать сна или мне нужно иметь его там?кажется, что мой интерфейс не будет обновлять лейбл, если я не сделаю?что мне кажется странным, так как я могу переместить окно приложения (оно не блокируется) - так почему бы не обновить ярлык, и как я могу изменить свой код, чтобы лучше поддерживать обновления Parallel.For (Each) и UI?

Я искал решение, но мне кажется, что я ничего не могу найти (или, может, я искал не то?).

С уважением, Саймон

Ответы [ 3 ]

4 голосов
/ 27 января 2011

У меня есть аналогичное требование для обновления моего графического интерфейса, поскольку результаты поступают в Parallel.ForEach ().Я пошел совсем по-другому, чем вы.Сделать здесь, вы можете определить, когда имеет смысл определить ваш прогресс через цикл.И, поскольку эти итерации цикла выполняются в фоновых потоках, вам необходимо перенаправить логику обновления элемента управления GUI обратно в ваш основной (диспетчерский) поток.Это простой пример - просто убедитесь, что вы следуете концепции, и все будет в порядке.

2 голосов
/ 24 января 2011

Я предполагаю, что подсчет до 25 миллионов (параллельно!) Занимает меньше секунды ... поэтому ваш таймер не сработает до завершения подсчета.Если вы добавите Thread.Sleep, все будет работать намного медленнее, чтобы вы могли видеть обновления.

С другой стороны, ваш обработчик событий таймера выглядит грязно.Вы порождаете цепочку для отправки сообщения в ваш пользовательский интерфейс, и когда вы, наконец, попадаете в свою ветку пользовательского интерфейса, вы вызываете Application.DoEvents ... почему?У вас должна быть возможность удалить как создание задачи, так и вызов DoEvents.

Редактировать : я протестировал опубликованную вами программу и дважды увидел обновление метки.Мой компьютер занимает более одной секунды, чтобы считать до 25м.Я увеличил число до 1 миллиарда, и метка обновляется несколько раз.

Edit2: Вы можете уменьшить обработчик таймера до

    private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        m_sync.Post((o) =>
        {
            label1.Text = m_count.ToString();
        }, null);
    }

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

Interlocked.Add(ref m_count, 1);
1 голос
/ 24 января 2011

для параллельной попытки

//private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)

System.Threading.Tasks.Task.Factory.StartNew(() =>
{
  m_sync.Post((o) =>
  {
    label1.Text = m_count.ToString();
    Application.DoEvents();
  }, null);

  System.Threading.Thread.Sleep(1000;)

}, System.Threading.Tasks.TaskCreationOptions.LongRunning);

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

eg. label1.Invoke(updateUIHandler);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...