A primeira é uma opção muito melhor.
Parallel.ForEach, internamente, usa a Partitioner<T>
para distribuir sua coleção em itens de trabalho. Ele não executará uma tarefa por item, mas fará o lote para reduzir a sobrecarga envolvida.
A segunda opção agendará um único Task
por item em sua coleção. Embora os resultados sejam (quase) os mesmos, isso apresentará muito mais sobrecarga do que o necessário, especialmente para coleções grandes, e fará com que os tempos de execução gerais sejam mais lentos.
FYI - O Partitioner usado pode ser controlado usando as sobrecargas apropriadas para Parallel.ForEach , se desejado. Para detalhes, consulte Particionadores personalizados no MSDN.
A principal diferença, em tempo de execução, é a segunda que será assíncrona. Isso pode ser duplicado usando Parallel.ForEach, fazendo o seguinte:
Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
Ao fazer isso, você ainda aproveita os particionadores, mas não bloqueia até que a operação esteja concluída.