2015-05-29 3 views
4

Я начал с попытки добавить индикатор выполнения в форму окна, которая обновляет ход кода, выполняющегося в цикле Parallel.Foreach. Для этого необходимо, чтобы поток пользовательского интерфейса был доступен для обновления индикатора выполнения. Я использовал задачу для запуска цикла Parallel.Foreach, чтобы позволить потоку пользовательского интерфейса обновлять индикатор выполнения.Использование задачи с Parallel.Foreach в .NET 4.0

Работа выполнена в рамках параллельного цикла. Форекс довольно интенсивный. После запуска исполняемых программ (не отладки в визуальной студии) с помощью Задачи программа перестала отвечать на запросы. Это не тот случай, если я запускаю свою программу без Task. Ключевое различие, которое я заметил между двумя экземплярами, заключается в том, что программа занимает ~ 80% от процессора, когда бегает без Task, и ~ 5% при запуске с помощью Task.

private void btnGenerate_Click(object sender, EventArgs e) 
    { 
     var list = GenerateList(); 
     int value = 0; 
     var progressLock = new object(); 

     progressBar1.Maximum = list.Count(); 

     Task t = new Task(() => Parallel.ForEach (list, item => 
     { 
       DoWork(); 
       lock (progressLock) 
       { 
        value += 1; 
       } 
     })); 

     t.Start(); 

     while (!t.IsCompleted) 
     { 
      progressBar1.Value = value; 
      Thread.Sleep (100); 
     } 
    } 

Side Примечание: Я знаю, что

Interlocked.Increment(ref int___); 

работы на месте замка. Считается ли это более эффективным?

Мой вопрос в три раза:

1.) Почему программа с Task зависать, когда нагрузка намного меньше?

2.) Использует ли Task для запуска Parallel.Foreach ограничивает пул потоков Parallel.Foreach только потоком, выполняющим задачу?

3.) Есть ли способ заставить пользовательский интерфейс реагировать вместо спящего для продолжительности .1 секунды без использования токена отмены?

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

+0

Ожидание выполнения задачи в цикле в потоке пользовательского интерфейса подобно выполнению его в потоке пользовательского интерфейса (немного хуже, даже). Посмотрите на «BackgroundWorker» или «async» – SimpleVar

+0

Если 'DoWork()' обращается к любому из ваших элементов интерфейса, это приведет к возникновению тупика. –

+0

JohnathonSullinger - DoWork() не имеет доступа к элементам пользовательского интерфейса. @Yorye Nathan - Не могли бы вы рассказать или привести пример? Я никогда не делал ничего с многопоточным до сегодняшнего дня. –

ответ

6

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

От MSDN:

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

Метод Invoke выполняет поиск родительской цепочки элемента управления до тех пор, пока не найдет элемент управления или форму, у которой есть дескриптор окна, если текущий дескриптор текущего элемента управления еще не существует.

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    string[] GenerateList() => new string[500]; 
    void DoWork() 
    { 
     Thread.Sleep(50); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     var list = GenerateList(); 
     progressBar1.Maximum = list.Length; 

     Task.Run(() => Parallel.ForEach(list, item => 
     { 
      DoWork(); 

      // Update the progress bar on the Synchronization Context that owns this Form. 
      this.Invoke(new Action(() => this.progressBar1.Value++)); 
     })); 
    } 
} 

Это будет вызывать Action делегат в том же UI потоке, что форма принадлежит, внутри задачи.

Теперь, чтобы попытаться ответить на ваши вопросы

1.) Почему программа с Task зависать, когда нагрузка намного меньше?

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

Вы также используете цикл while, который сглажает поток пользовательского интерфейса каждые 100 миллисекунд. Вы увидите, что UI висит из-за этого цикла while.

2.) Использует ли Task для запуска Parallel.Foreach ограничивает пул потоков Parallel.Foreach только потоком, выполняющим задачу?

Это не так. В вызове Parallel.ForEach будет создано несколько задач. Основополагающий ForEach использует partitioner для распространения работы и не создает больше задач, чем того, что необходимо. Он создает задачи партиями и обрабатывает партии.

3.) Есть ли способ заставить пользовательский интерфейс реагировать вместо спящего для продолжительности .1 секунды без использования токена отмены?

Я был в состоянии справиться с этим путем удаления петли while и используя метод Invoke просто идти вперед и выполнить лямбда в потоке пользовательского интерфейса напрямую.

+0

Это отличное решение для .Net 4.5, но вопрос отмечен .net-4.0. Может ли OP использовать ['BeginInvoke()'] (https://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke%28v=vs.110%29.aspx) для обновления индикатор прогресса? – dbc

+0

А, я пропустил это. Да, они могут. Я верну свой ответ на примере. –

+0

Спасибо за ответ! К сожалению, проект, над которым я работаю, использует .NET 4.0, который, насколько мне известно, не поддерживает класс Progress (T). Редактировать: Не видел, что кто-то уже поймал это. –

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