Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Em muitos casos, o PLINQ pode fornecer melhorias significativas de desempenho em relação às consultas LINQ to Objects sequenciais. No entanto, o trabalho de paralelizar a execução da consulta introduz complexidade que pode levar a problemas que, no código sequencial, não são tão comuns ou não são encontrados. Este tópico lista algumas práticas a serem evitadas ao escrever consultas PLINQ.
Não suponha que o paralelo seja sempre mais rápido
Às vezes, a paralelização faz com que uma consulta PLINQ seja executada mais lentamente do que seu LINQ to Objects equivalente. A regra básica é que as consultas que têm poucos elementos fonte e delegados de usuários rápidos provavelmente não acelerarão muito. No entanto, como muitos fatores estão envolvidos no desempenho, recomendamos medir os resultados reais antes de decidir se deseja usar o PLINQ. Para obter mais informações, consulte Noções básicas sobre a aceleração no PLINQ.
Evite gravar em locais de memória compartilhada
No código sequencial, não é incomum ler ou gravar em variáveis estáticas ou campos de classe. No entanto, sempre que vários threads estão acessando essas variáveis simultaneamente, existe um grande potencial para condições de corrida. Embora você possa usar bloqueios para sincronizar o acesso à variável, o custo da sincronização pode prejudicar o desempenho. Portanto, recomendamos que você evite, ou pelo menos limite, o acesso ao estado compartilhado em uma consulta PLINQ o máximo possível.
Evitar paralelização excessiva
Usando o método AsParallel, você arca com os custos gerais de particionamento da coleção de origem e sincronização dos threads de trabalho. Os benefícios da paralelização são ainda mais limitados pelo número de processadores no computador. Não há nenhuma aceleração a ser obtida executando vários threads associados à computação em apenas um processador. Portanto, você deve ter cuidado para não paralelizar excessivamente uma consulta.
O cenário mais comum em que a paralelização excessiva pode ocorrer é em consultas aninhadas, como mostrado no seguinte snippet.
var q = from cust in customers.AsParallel()
from order in cust.Orders.AsParallel()
where order.OrderDate > date
select new { cust, order };
Dim q = From cust In customers.AsParallel()
From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}
Nesse caso, é melhor paralelizar apenas a fonte de dados externa (clientes), a menos que uma ou mais das seguintes condições se apliquem:
A fonte de dados interna (cust.Orders) é conhecida por ser muito longa.
Em cada pedido, você está realizando uma computação dispendiosa. (A operação mostrada no exemplo não é cara.)
Sabe-se que o sistema-alvo tem processadores suficientes para lidar com o número de threads que serão produzidos por meio da paralelização da consulta em
cust.Orders.
Em todos os casos, a melhor maneira de determinar a forma de consulta ideal é testar e medir. Para obter mais informações, consulte Como medir o desempenho da consulta PLINQ.
Evite chamadas para métodos não thread-safe
Gravar para métodos de instância não thread-safe a partir de uma consulta PLINQ pode levar à corrupção de dados que pode ou não ser detectada no seu programa. Ele também pode levar a exceções. No exemplo a seguir, vários threads estariam tentando chamar o FileStream.Write método simultaneamente, o que não é compatível com a classe.
Dim fs As FileStream = File.OpenWrite(…)
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))
FileStream fs = File.OpenWrite(...);
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(x => fs.Write(x));
Limite chamadas para métodos thread-safe
A maioria dos métodos estáticos no .NET são thread-safe e podem ser chamados de vários threads simultaneamente. No entanto, mesmo nesses casos, a sincronização envolvida pode levar a uma desaceleração significativa na consulta.
Observação
Você pode testar isso sozinho inserindo algumas chamadas para WriteLine nas suas consultas. Embora esse método seja usado nos exemplos de documentação para fins de demonstração, não use-o em consultas PLINQ.
Evitar operações de ordenação desnecessárias
Quando o PLINQ executa uma consulta em paralelo, ele divide a sequência de origem em partições que podem ser operadas simultaneamente em vários threads. Por padrão, a ordem na qual as partições são processadas e os resultados são entregues não é previsível (exceto para operadores como OrderBy). Você pode instruir o PLINQ a preservar a ordenação de qualquer sequência de origem, mas isso tem um impacto negativo no desempenho. A melhor prática, sempre que possível, é estruturar consultas para que elas não dependam da preservação da ordem. Para saber mais, veja Preservação da ordem em PLINQ.
Prefira ForAll a ForEach quando possível
Embora o PLINQ execute uma consulta em múltiplos threads, se você consumir os resultados em um loop foreach (For Each no Visual Basic), os resultados da consulta devem ser mesclados novamente em um thread e acessados em série pelo enumerador. Em alguns casos, isso é inevitável; no entanto, sempre que possível, use o ForAll método para habilitar cada thread para gerar seus próprios resultados, por exemplo, gravando em uma coleção thread-safe, como System.Collections.Concurrent.ConcurrentBag<T>.
O mesmo problema se aplica a Parallel.ForEach. Em outras palavras, source.AsParallel().Where().ForAll(...) deve ser fortemente preferido para Parallel.ForEach(source.AsParallel().Where(), ...).
Esteja ciente de questões de afinidade de thread
Algumas tecnologias, por exemplo, interoperabilidade COM para componentes de um único segmento (STA), Windows Forms e Windows Presentation Foundation (WPF), impõem restrições de afinidade de thread que exigem que o código seja executado em um thread específico. Por exemplo, nos Windows Forms e no WPF, um controle só pode ser acessado no thread no qual foi criado. Se você tenta acessar o estado compartilhado de um controle Windows Forms em uma consulta PLINQ, uma exceção é gerada se você estiver executando no depurador. (Essa configuração pode ser desativada.) No entanto, se a consulta for consumida no thread da interface do usuário, você poderá acessar o controle do foreach loop que enumera os resultados da consulta porque esse código é executado em apenas um thread.
Não suponha que iterações de ForEach, For e ForAll sempre sejam executadas em paralelo
É importante ter em mente que as iterações individuais em um loop de Parallel.For, Parallel.ForEach ou ForAll podem ser executadas, mas não precisam ser executadas em paralelo. Portanto, você deve evitar escrever qualquer código que dependa da correção da execução paralela de iterações ou da execução de iterações em qualquer ordem específica.
Por exemplo, é provável que esse código esteja em deadlock:
Dim mre = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Wait()
End If
End Sub) ' deadlocks
ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks
Neste exemplo, uma iteração define um evento e todas as outras iterações esperam no evento. Nenhuma das iterações de espera pode ser finalizada até que a iteração de definição de evento seja concluída. No entanto, é possível que as iterações de espera bloqueiem todos os threads usados para executar o loop paralelo, antes que a iteração de configuração de evento tenha tido a chance de ser executada. Isso resulta em um deadlock – a iteração de definição de eventos nunca será executada e as iterações de espera nunca serão despertadas.
Em particular, uma iteração de um loop paralelo nunca deve aguardar outra iteração do loop para progredir. Se o loop paralelo decidir agendar as iterações sequencialmente, mas na ordem oposta, ocorrerá um deadlock.