Как вы используете AsParallel с асинхронными и ожидающими ключевыми словами?
Я искал пример кода для async и заметил несколько проблем с тем, как он был реализован. В то время как я смотрел на код, я задавался вопросом, было бы более эффективным перебирать список, используя как параллельный, а не просто циклически перебирать список.
Насколько я могу судить, разница в производительности очень мала, и каждый использует каждый процессор, и оба общаются примерно столько же времени, сколько завершено.
Это первый способ сделать это
var tasks= Client.GetClients().Select(async p => await p.Initialize());
И это вторая
var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());
Правильно ли я предполагаю, что между ними нет разницы?
Полная программа может быть найдена ниже
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
RunCode1();
Console.WriteLine("Here");
Console.ReadLine();
RunCode2();
Console.WriteLine("Here");
Console.ReadLine();
}
private async static void RunCode1()
{
Stopwatch myStopWatch = new Stopwatch();
myStopWatch.Start();
var tasks= Client.GetClients().Select(async p => await p.Initialize());
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Time ellapsed(ms): " + myStopWatch.ElapsedMilliseconds);
myStopWatch.Stop();
}
private async static void RunCode2()
{
Stopwatch myStopWatch = new Stopwatch();
myStopWatch.Start();
var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Time ellapsed(ms): " + myStopWatch.ElapsedMilliseconds);
myStopWatch.Stop();
}
}
class Client
{
public static IEnumerable<Client> GetClients()
{
for (int i = 0; i < 100; i++)
{
yield return new Client() { Id = Guid.NewGuid() };
}
}
public Guid Id { get; set; }
//This method has to be called before you use a client
//For the sample, I don't put it on the constructor
public async Task Initialize()
{
await Task.Factory.StartNew(() =>
{
Stopwatch timer = new Stopwatch();
timer.Start();
while(timer.ElapsedMilliseconds<1000)
{}
timer.Stop();
});
Console.WriteLine("Completed: " + Id);
}
}
}
Ответы
Ответ 1
Там должно быть очень мало различимых различий.
В вашем первом случае:
var tasks = Client.GetClients().Select(async p => await p.Initialize());
Выполняющий поток (по одному за раз) запускает Initialize
для каждого элемента в списке клиентов. Initialize
немедленно ставит очередь в пул потоков и возвращает незавершенную Task
.
Во втором случае:
var tasks = Client.GetClients().AsParallel().Select(async p => await p.Initialize());
Выполняющий поток будет вилкой в пуле потоков и (параллельно) начнет выполнение Initialize
для каждого элемента в списке клиентов. Initialize
имеет такое же поведение: она немедленно ставит очередь в пул потоков и возвращает их.
Два тайминга почти идентичны, потому что вы только распараллеливаете небольшое количество кода: очередь процесса в пул потоков и возврат незавершенной Task
.
Если Initialize
выполняла более длительную (синхронную) работу до ее первого await
, может иметь смысл использовать AsParallel
.
Помните, что все async
методы (и lambdas) запускаются синхронно (см. Официальный FAQ или мою собственную запись в intro).
Ответ 2
Там необычайно большое различие.
В следующем коде вы берете на себя обязательство выполнить разбиение. Другими словами, вы создаете один объект Task
каждого элемента из IEnumerable<T>
который возвращается из вызова GetClients()
:
var tasks= Client.GetClients().Select(async p => await p.Initialize());
Во втором случае вызов AsParallel
внутренне собирается использовать экземпляры Task
для выполнения разделов IEnumerable<T>
и у вас будет начальная Task
которая возвращается из lambda async p => await p.Initialize()
:
var tasks = Client.GetClients().AsParallel().
Select(async p => await p.Initialize());
Наконец, вы ничего не делаете, используя async
/ await
здесь. Конечно, компилятор может оптимизировать это, но вы просто ожидаете метода, который возвращает Task
а затем возвращает продолжение, которое ничего не делает через лямбда. Тем не менее, поскольку вызов Initialize
уже возвращает Task
, лучше всего держать его простым и просто делать:
var tasks = Client.GetClients().Select(p => p.Initialize());
Который вернет вам последовательность экземпляров Task
.
Ответ 3
Чтобы улучшить приведенные выше ответы, это самый простой способ получить ожидаемое выполнение async/threaded:
var results = await Task.WhenAll(Client.GetClients(). Выберите (async p => p.Initialize()));
Это гарантирует, что он будет вращать отдельные потоки, и вы получите результаты в конце. Надежда помогает кому-то. Мне потребовалось некоторое время, чтобы понять это правильно, потому что это очень не очевидно, и функция AsParallel() кажется тем, что вы хотите, но не использует async/wait.