2014-02-10 8 views
0

Я знаю, что нет необходимости ждать Parallel.For, но поскольку UI сообщения Windows (насос сообщений WinForms) обрабатываются во время Parallel.For, я хотел бы подождать Parallel.For, чтобы закончить.В ожидании Parallel.For

Неплохо ли инкапсулировать Parallel.For внутри Task, а затем ждать его? Есть ли лучший подход?

Спасибо.

CancellationTokenSource token = new CancellationTokenSource(); 

const int len = 100; 

double[] array = new double[len]; 

Task t = Task.Factory.StartNew(delegate { 
    Parallel.For(0, len, delegate(int i, ParallelLoopState loopState) { 

    array[i] += 1; 

    }); 

try 
{ 
    t.Wait(token.Token); 
} 
catch (OperationCanceledException e) 
{ 
    rc = false; 
} 
+3

'Parallel.For' уже блокирует текущий поток до тех пор, пока все задачи не будут выполнены. –

+0

'Parallel.For' не возвращается до его завершения. Если вы завернете его в «Задачу», тогда эта задача будет завершена «точно» в тот же момент времени, в который возвращается «Parallel.For». Вы ничего не добавили. –

+0

Это неправда, сообщения WM_PAINT прибывают обрабатываемыми ... – abenci

ответ

1

Вместо Parallel.For почему бы не использовать только Task и вызвать Task.WaitAll()?

var t1 = Task.Run(() => {}); 
var t2 = Task.Run(() => {}); 

Task.WaitAll(t1, t2); 
+0

Parallel.For() разбивает работу на нескольких ядрах процессора. – abenci

+1

Well Task.Run можно планировать и в отдельной ветке. – Mayank

+0

Также вы можете использовать 'Parallel.Invoke' с' Task'. – Mayank

1

Если вы хотите ждать Parallel.For(), чтобы закончить, вы просто не запустить его в отдельную задачу!

Parallel.For() не возвращается до его завершения (даже если для выполнения работы используется несколько потоков).

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

+0

Это неверно для сообщений UI 'WM_', извините ... – abenci

+0

@Alberto Да, это правда. Просто добавьте отладочные сообщения вокруг вызовов. То, что вы видите, - это цикл сообщения, накачиваемый при запуске Parallel.For(), но Parallel.For() НЕ возвращается, пока он не завершится. –

+0

Пожалуйста, попробуйте, прежде чем комментировать ... Мы проверили его дважды, прежде чем добавлять этот вопрос. – abenci

3

Почему вы так уверены, что WM_PAINT и другие сообщения Windows перекачиваются, а Parallel.For или Task.Wait блокирует поток пользовательского интерфейса?

Следующий простой пример доказывает, что вы ошибаетесь. Форма не окрашивается в красный цвет в течение 15 секунд, тогда как работает Parallel.For.

using System; 
using System.Diagnostics; 
using System.Drawing; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace WinFroms_21681229 
{ 
    public partial class MainForm : Form 
    { 
     public MainForm() 
     { 
      InitializeComponent(); 

      this.Shown += MainForm_Shown; 
     } 

     void MainForm_Shown(object sender, EventArgs e) 
     { 
      MessageBox.Show("Before"); 

      this.BackColor = Color.FromName("red"); 
      this.Invalidate(); 

      // the form is not getting red for another 15 seconds 
      var array = new double[] { 1, 2, 3 }; 
      Parallel.For(0, array.Length, (i) => 
      { 
       System.Threading.Thread.Sleep(5000); 
       Debug.Print("data: " + array[i]); 
      }); 

      MessageBox.Show("After"); 
     } 
    } 
} 

Вот как выполнять задачи параллельно, сохраняя при этом пользовательский интерфейс отзывчивый:

async void MainForm_Shown(object sender, EventArgs e) 
{ 
    MessageBox.Show("Before"); 

    this.BackColor = Color.FromName("red"); 
    this.Invalidate(); 

    // the form is not getting red for another 15 seconds 
    var array = new double[] { 1, 2, 4 }; 

    var tasks = array.Select((n) => Task.Run(()=> 
    { 
     Thread.Sleep(5000); 
     Debug.Print("data: " + n); 
    })); 

    await Task.WhenAll(tasks); 

    MessageBox.Show("After"); 
} 

Вы могли бы сделать что-то вроде await Task.Factory.StartNew(() => Parallel.For(...)), но это будет использовать больше, по крайней мере один поток, чем действительно необходимо.

Чтобы понять, что происходит за сценой здесь, вы должны понять, как петля WinForms сообщение работает внутри Application.Run, как await дает контроль выполнения обратно в цикл обработки сообщений, а затем получает по-прежнему с помощью WindowsFormsSynchronizationContext, когда задача имеет завершено. async-await tag wiki может помочь, он содержит ссылки на некоторые большие, необходимые для чтения ресурсы.

Если вам интересно узнать, как работает перекачка сообщений во время операций блокировки, отметьте this answer.

+1

Небольшое предложение, если вы собираетесь выполнять задание, для завершения которого требуется более нескольких секунд, вы не должны использовать 'Task.Run (' как это будет связывать поток пула потоков, вместо этого используйте ['Task.Factory. StartNew (') (http://msdn.microsoft.com/en-us/library/dd321263%28v=vs.110%29.aspx) и перейдите в' TaskCreationOptions.LongRunning'. Это будет (если использовать задачу по умолчанию планировщик) заставляют вашу задачу запускаться на полном 'Thread' с набором' IsBackground' вместо потока ThreadPool. –

+0

@ScottChamberlain, отличный момент, спасибо! – Noseratio

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