Метод Task.WaitAll vs Parallel.Invoke
У меня есть пример кода для сравнения времени обработки для параллельного подхода и подхода Task. Целью этого эксперимента является понимание того, как они работают.
Итак, мои вопросы:
- Почему Parallel работала быстрее, чем Task?
- Имеют ли мои результаты, что я должен использовать Parallel вместо Task?
- Где я должен использовать Task и где Parallel?
- Какие преимущества использования Задачи по сравнению с Parallel?
-
Есть ли задача только для метода ThreadPool.QueueUserWorkItem?
public Task SomeLongOperation()
{
return Task.Delay(3000);
}
static void Main(string[] args)
{
Program p = new Program();
List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
var arr = tasks.ToArray();
Stopwatch sw = Stopwatch.StartNew();
Task.WaitAll(arr);
Console.WriteLine("Task wait all results: " + sw.Elapsed);
sw.Stop();
sw = Stopwatch.StartNew();
Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
sw.Stop();
Console.ReadKey();
}
Вот мои результаты обработки:
![results]()
EDIT:
Измененный код выглядит следующим образом:
Program p = new Program();
Task[] tasks = new Task[2];
Stopwatch sw = Stopwatch.StartNew();
tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());
Task.WaitAll(tasks);
Console.WriteLine("Task wait all results: " + sw.Elapsed);
sw.Stop();
sw = Stopwatch.StartNew();
Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
sw.Stop();
Мои новые результаты:
![new results]()
ИЗМЕНИТЬ 2:
Когда я заменил код Parallel.Invoke, чтобы быть первым, и Task.WaitAll будет второй, ситуация кардинально изменилась. Теперь Parallel работает медленнее. Это заставляет меня думать о некорректности моих оценок. Я изменил код, чтобы выглядеть так:
Program p = new Program();
Task[] tasks = new Task[2];
Stopwatch sw = null;
for (int i = 0; i < 10; i++)
{
sw = Stopwatch.StartNew();
Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
string res = sw.Elapsed.ToString();
Console.WriteLine("Parallel invoke results: " + res);
sw.Stop();
}
for (int i = 0; i < 10; i++)
{
sw = Stopwatch.StartNew();
tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());
Task.WaitAll(tasks);
string res2 = sw.Elapsed.ToString();
Console.WriteLine("Task wait all results: " + res2);
sw.Stop();
}
И вот мои новые результаты:
![enter image description here]()
![enter image description here]()
Теперь я могу предположить, что этот эксперимент намного яснее. Результаты почти одинаковы. Иногда Параллельная, а иногда и задача быстрее. Теперь мои вопросы:
1. Где я должен использовать Task и где Parallel?
2. Какие преимущества использования Задачи по сравнению с Parallel?
3. Задача - это просто обертка для метода ThreadPool.QueueUserWorkItem?
Любая полезная информация, которая может прояснить эти вопросы, приветствуется.
Ответы
Ответ 1
EDIT от эта статья от MSDN
Обе параллели и задачи являются оболочками для ThreadPool. Параллельный вызов также ожидает, пока все задачи не будут завершены.
Относительно ваших вопросов:
Использование Task, Parallel или ThreadPool зависит от гранулярности управления, которое необходимо выполнить при выполнении ваших параллельных задач. Я лично привык к Task.Factory.StartNew()
, но это личное мнение. То же самое относится к ThreadPool.QueueUserWorkItem()
Дополнительная информация: Первый вызов Parallel.Invoke() и Task.Factory.StartNew() может быть медленнее из-за внутренней инициализации.
Ответ 2
Если вы начинаете несрочные задачи и сразу Wait
для них, используйте Parallel.Invoke
. Ваше намерение сразу становится понятным читателю.
Используйте Задачи, если:
- вы не ждете сразу
- вам нужны возвращаемые значения
- вам нужно указать параметры для методов, называемых
- вам требуется
TaskCreationOptions
функциональность
- вам нужны функции
CancellationToken
или TaskScheduler
и не хотят использовать ParallelOptions
Да, вы можете обойти некоторые из них, например. Parallel.Invoke(() => p.OpWithToken(CancellationToken)
, но это запутывает ваши намерения. Parallel.Invoke
предназначен для выполнения кучи работы, используя как можно больше мощности процессора. Это делается, он не заторможен, и вы знаете это заранее.