Formação
Módulo
Iterar através da utilização de bloco de código para uma instrução em C# - Training
Use a instrução for iteration para repetir um número predefinido de vezes e controlar o processo de iteração.
Este browser já não é suportado.
Atualize para o Microsoft Edge para tirar partido das mais recentes funcionalidades, atualizações de segurança e de suporte técnico.
Em muitos casos, Parallel.For e Parallel.ForEach pode fornecer melhorias significativas de desempenho em relação a loops sequenciais comuns. No entanto, o trabalho de paralelização do loop introduz complexidade que pode levar a problemas que, em 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 quando você escreve loops paralelos.
Em certos casos, um loop paralelo pode ser executado mais lentamente do que seu equivalente sequencial. A regra básica é que loops paralelos que têm poucas iterações e delegados de usuário rápidos provavelmente não acelerarão muito. No entanto, como muitos fatores estão envolvidos no desempenho, recomendamos que você sempre meça os resultados reais.
Em 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 a aceder a essas variáveis simultaneamente, há 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 um loop paralelo tanto quanto possível. A melhor maneira de fazer isso é usar as sobrecargas de Parallel.For e Parallel.ForEach que usam uma variável System.Threading.ThreadLocal<T> para armazenar o estado local do thread durante a execução do loop. Para obter mais informações, consulte Como escrever um bloco de loops de Parallel.For com variáveis de Thread-Local e Como escrever um bloco de loops de Parallel.ForEach com variáveis de Partition-Local.
Ao utilizares ciclos paralelos, incorres nos custos gerais de particionar a coleção de origem e sincronizar os 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 ligados à computação em apenas um processador. Portanto, você deve ter cuidado para não paralelizar demais um loop.
O cenário mais comum em que a paralelização excessiva pode ocorrer é em loops aninhados. Na maioria dos casos, é melhor paralelizar apenas o loop externo, a menos que uma ou mais das seguintes condições se apliquem:
O loop interno é conhecido por ser muito longo.
Você está realizando um cálculo dispendioso em cada pedido. (A operação mostrada no exemplo não é cara.)
O sistema de destino é conhecido por ter processadores suficientes para lidar com o número de threads que serão produzidos por paralelização do processamento.
Em todos os casos, a melhor maneira de determinar a forma de consulta ideal é testar e medir.
Escrever em métodos de instância não seguros para threads a partir de um loop paralelo pode provocar corrupção de dados, que pode ou não ser detectada no seu programa. Pode também conduzir a exceções. No exemplo a seguir, várias threads estariam tentando chamar simultaneamente o método FileStream.WriteByte, o que não é suportado pela classe.
FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))
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 lentidão significativa na consulta.
Nota
Você mesmo pode testar isso inserindo algumas chamadas para WriteLine em suas consultas. Embora este método seja usado nos exemplos de documentação para fins de demonstração, não o use em loops paralelos, a menos que necessário.
Algumas tecnologias, por exemplo, interoperabilidade COM para componentes STA (Single-Threaded Apartment), 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, no Windows Forms e no WPF, um controle só pode ser acessado no thread no qual foi criado. Isso significa, por exemplo, que você não pode atualizar um controle de lista a partir de um loop paralelo, a menos que configure o agendador de threads para agendar o trabalho somente no thread da interface do usuário. Para obter mais informações, consulte Especificando um contexto de sincronização.
Em determinadas circunstâncias, a Biblioteca Paralela de Tarefas poderá embutir uma tarefa, o que significa que ela é executada no thread atualmente em execução. (Para obter mais informações, consulte Agendadores de tarefas.) Essa otimização de desempenho pode levar a um impasse em certos casos. Por exemplo, duas tarefas podem executar o mesmo código delegado, que sinaliza quando ocorre um evento e, em seguida, aguarda o sinal da outra tarefa. Se a segunda tarefa estiver embutida no mesmo thread que a primeira e a primeira entrar em um estado de espera, a segunda tarefa nunca poderá sinalizar seu evento. Para evitar tal ocorrência, você pode especificar um tempo limite na operação Wait ou usar construtores de thread explícitos para ajudar a garantir que uma tarefa não possa bloquear a outra.
É importante ter em mente que iterações individuais em um For, ForEach ou ForAll loop podem, mas não têm de ser executadas em paralelo. Portanto, você deve evitar escrever qualquer código que dependa para a 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 este código bloqueie:
ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100)
.AsParallel()
.ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine($"Set on {Thread.CurrentThread.ManagedThreadId} with value of {j}");
mre.Set();
}
else
{
Console.WriteLine($"Waiting on {Thread.CurrentThread.ManagedThreadId} with value of {j}");
mre.Wait();
}
}); //deadlocks
Dim mres = 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)
mres.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Wait()
End If
End Sub) ' deadlocks
Neste exemplo, uma iteração define um evento e todas as outras iterações aguardam o evento. Nenhuma das iterações em espera pode ser concluída até que a iteração de configuração de eventos seja concluída. No entanto, é possível que as iterações em espera bloqueiem todos os threads usados para executar o loop paralelo, antes que a iteração de configuração de eventos tenha tido a chance de ser executada. Isso resulta em um impasse – a iteração de configuração de eventos nunca será executada e as iterações em espera nunca serão ativadas.
Em particular, uma iteração de um loop paralelo nunca deve esperar por outra iteração do loop para progredir. Se o loop paralelo decidir agendar as iterações sequencialmente, mas na ordem oposta, ocorrerá um impasse.
É importante manter a interface do usuário (UI) do seu aplicativo responsiva. Se uma operação contiver trabalho suficiente para garantir paralelização, ela provavelmente não deve ser executada no thread da interface do usuário. Em vez disso, deve descarregar essa operação para ser executada num thread em segundo plano. Por exemplo, se você quiser usar um loop paralelo para calcular alguns dados que devem ser renderizados em um controle de interface do usuário, considere executar o loop em uma instância de tarefa em vez de diretamente em um manipulador de eventos de interface do usuário. Somente quando a computação principal estiver concluída, você deverá organizar a atualização da interface do usuário de volta para o thread da interface do usuário.
Se você executar loops paralelos no thread da interface do usuário, tenha cuidado para evitar a atualização dos controles da interface do usuário de dentro do loop. A tentativa de atualizar os controles da interface do usuário de dentro de um loop paralelo que está sendo executado no thread da interface do usuário pode levar a corrupção de estado, exceções, atualizações atrasadas e até mesmo bloqueios, dependendo de como a atualização da interface do usuário é invocada. No exemplo a seguir, o loop paralelo bloqueia o thread da interface do usuário no qual está sendo executado até que todas as iterações sejam concluídas. No entanto, se uma iteração do loop estiver sendo executada em um thread em segundo plano (como For pode fazer), a chamada para Invoke fará com que uma mensagem seja enviada para o thread da interface do usuário e bloqueará a espera que essa mensagem seja processada. Como o thread da interface do usuário está bloqueado executando o For, a mensagem nunca pode ser processada, logo, o thread da interface do usuário entra em deadlock.
private void button1_Click(object sender, EventArgs e)
{
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
});
}
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub)
End Sub
O exemplo a seguir mostra como evitar o deadlock, executando o loop dentro de uma instância de tarefa. O thread da interface do usuário não é bloqueado pelo loop e a mensagem pode ser processada.
private void button2_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
})
);
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub))
End Sub
Comentários do .NET
O .NET é um projeto código aberto. Selecione um link para fornecer comentários:
Formação
Módulo
Iterar através da utilização de bloco de código para uma instrução em C# - Training
Use a instrução for iteration para repetir um número predefinido de vezes e controlar o processo de iteração.