2011-01-24 4 views
4

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

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(), кажется, чтобы дать время для обновления UI потока, чтобы делать свою работу:

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

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

Ive искал решение, но я не могу найти что-либо (или я, возможно, искал неправильную вещь?).

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

+0

Я не знаю много об этом, но может ли это быть чем-то связанным с количеством разрешенных потоков? Если Parallel.For забирает их все и не оставляет для захвата события, то это может объяснить поведение. Но я не уверен в этом, поэтому на самом деле это просто дикое предположение, что, надеюсь, вдохновит кого-то, кто знает больше. :) – Chris

+0

Попробуйте положить Application.DoEvents() после установки текста метки. Это позволит другим потокам (в частности, потоку пользовательского интерфейса) получить возможность перерисовать экран – user2712361

ответ

2

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

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

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

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); 
+0

Спасибо, это работает :) – SimonDK

+1

Не следует изменять, но это также доступно: 'Interlocked.Increment (ref m_count)' – patridge

1

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

//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); 

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

eg. label1.Invoke(updateUIHandler); 
4

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

public partial class ClassThatUsesParallelProcessing 
{ 
    public event ProcessingStatusEvent StatusEvent; 

    public ClassThatUsesParallelProcessing() 
    { } 

    private void doSomethingInParallel() 
    { 
     try 
     { 
      int counter = 0; 
      int total = listOfItems.Count; 

      Parallel.ForEach(listOfItems, (instanceFromList, state) => 
      { 
       // do your work here ... 

       // determine your progress and fire event back to anyone who cares ... 
       int count = Interlocked.Increment(ref counter); 

       int percentageComplete = (int)((float)count/(float)total * 100); 
       OnStatusEvent(new StatusEventArgs(State.UPDATE_PROGRESS, percentageComplete)); 
      } 
     } 
     catch (Exception ex) 
     { 

     } 
    } 
} 

Ваш GUI затем есть что-то похожее на следующее:

private void ProcessingStatusEventHandler(object sender, StatusEventArgs e) 
{ 
    try 
    { 
     if (e.State.Value == State.UPDATE_PROGRESS) 
     { 
      this.BeginInvoke((ProcessHelperDelegate)delegate 
      { 
       this.progressBar.Value = e.PercentageComplete; 
      } 
     } 
    } 
    catch { } 
} 

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

+0

Привет, спасибо за ваш ответ, я действительно думал об этом с событиями, но мне нужен таймер для обновления часов (время, затраченное на выполнение задачи) в любом случае, поэтому я просто положил его туда. – SimonDK

Смежные вопросы