Nos artigos anteriores sobre a Task Parallel Library (TPL) foram referidos os métodos Parallel.For e Parallel.ForEach que servem essencialmente para criar ciclos que podem correr em paralelo. Este método, o Parallel.Invoke, serve para executar processos que decorrem eventualmente em paralelo.
Quando os processos são invocados, são separados nos diversos núcleos do processador e o código só avança quando todos tiverem terminado.
Eis um exemplo do funcionamento:
Imports System.Threading
Imports System.Threading.Tasks
Module Module1
Private Sub task1()
For x As Integer = 0 To 50
Console.WriteLine("Task1 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub
Private Sub task2()
For x As Integer = 0 To 50
Console.WriteLine("Task2 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub
Sub Main()
' Chama as diversas acções (incluindo uma Lambda)
Dim actions() As Action = {AddressOf task1, AddressOf task2,
Sub()
' Usando uma Lambda Expression
For x As Integer = 0 To 50
Console.WriteLine("Task3 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub}
Parallel.Invoke(actions)
Console.WriteLine("Acções completas!")
End Sub
End Module
Executando o mesmo, mas sequencialmente (onde o tempo de execução é significativamente superior):
Imports System.Threading
Imports System.Threading.Tasks
Module Module1
Private Sub task1()
For x As Integer = 0 To 50
Console.WriteLine("Task1 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub
Private Sub task2()
For x As Integer = 0 To 50
Console.WriteLine("Task2 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub
Sub Main()
task1()
task2()
For x As Integer = 0 To 50
Console.WriteLine("Task3 - {0}", x.ToString())
System.Threading.Thread.SpinWait(50000000)
Next
End Sub
End Module
E é o terceiro e último evento da classe Parallel. Como se pode ver nos exemplos apresentados(For, ForEach e Invoke), são muito simples de utilizar e vai de certeza ajudar a melhorar o desempenho das aplicações.
Este método executa um ciclo semelhante à instrução For Each, mas que possibilita a execução em paralelo. É muito semelhante ao exemplo do artigo anterior, mas difere do Parallel.For() pois é usado quando os dados são colecções ou arrays.
Eis um exemplo da sua implementação/utilização:
Imports System.Threading
Imports System.Threading.Tasks
Module Module1
Sub Main()
Dim result1, result2 As Long
Dim values = Enumerable.Range(0, 49)
Dim sw As New Stopwatch
sw.Start()
' ------------------------------------------------
' Ciclo For Each
' ------------------------------------------------
For Each value As Integer In values
Console.WriteLine("Value:{0} GUI:{1}", value.ToString(), Guid.NewGuid())
Thread.SpinWait(50000000)
Next
result1 = sw.ElapsedMilliseconds
sw.Restart() ' <- novo método no Visual Studio 2010
' ------------------------------------------------
' Ciclo Parallel.ForEach
' ------------------------------------------------
Parallel.ForEach(values, Sub(value)
Console.WriteLine("Value:{0} GUI:{1}",
value.ToString(),
Guid.NewGuid())
Thread.SpinWait(50000000)
End Sub)
result2 = sw.ElapsedMilliseconds
sw.Stop()
Console.WriteLine("Tempo de execução 'For Each' {0} ms", result1)
Console.WriteLine("Tempo de execução 'Parallel.ForEach' {0} ms", result2)
Console.ReadKey(True)
End Sub
End Module
E os resultados são uma vez mais significativos:

É mais um ciclo disponível da Task Parallel Library (TPL) – Parallel Extensions, que quando bem aplicado pode melhorar bastante o desempenho da aplicação.
Data parallelism refere-se a situações em que a mesma operação é realizada simultaneamente (ou seja em paralelo) em elementos de uma colecção ou array. A classe System.Threading.Tasks.Parallel, do namespace System.Threading.Tasks, fornece um conjunto de métodos que permitem a execução de ciclos. Com a utilização do Parallel.For, Parallel.ForEach e Parallel.Invoke, incluindo inúmeros overloads disponíveis, é possível melhorar o desempenho das aplicações.
O método Parallel.For executa um ciclo que pode correr em paralelo. O sintaxe é o seguinte: Parallel.For(Int32, Int32, Action(Of Int32))
Executando um ciclo simples ou “normal”:
Dim sw As New Stopwatch()
sw.Start()
' Executa o método For ... Next
For x As Integer = 0 To 500
Console.WriteLine("Thread ID={0} - x={1}",
Thread.CurrentThread.ManagedThreadId, x)
Thread.Sleep(100)
Next x
Console.WriteLine("Tempo de execução: {0} ms", sw.ElapsedMilliseconds)
sw.Stop()
Como é possível ver nos seguintes resultados, o tempo de execução é longo, e o valor da variável x que é mostrado é continuo. A thread é também sempre a mesma (neste caso 9).

Para realizarmos o mesmo ciclo, mas desta vez recorrendo às Parallel Extensions, e melhorando desta forma o desempenho, podemos usar o método Parallel.For. Neste exemplo é usado Lambda para mostrar os resultados:
Dim sw As New Stopwatch()
sw.Start()
' Executa o método Parallel.For()
Parallel.For(0, 500, Sub(x)
Console.WriteLine("Thread ID={0} - x={1}",
Thread.CurrentThread.ManagedThreadId, x)
Thread.Sleep(100)
End Sub)
Console.WriteLine("Tempo de execução: {0} ms", sw.ElapsedMilliseconds)
sw.Stop()
Como é possível nos seguintes resultado, o tempo de execução é inferior, sendo usadas diversas threads, que são distribuídas e executadas nos diversos núcleos do processador. Isto justifica também porque o valor de x não é mostrado de forma continua.

Os resultados falam por si, ma é importante lembrar, que num exemplo simples sem que existam tarefas complexas (que possam demorar algum tempo a executar), a utilização dos métodos Parallel.For ou Parallel.ForEach é mais lento do que um ciclo simples (For … Next ou For Each … Next). Isto porque em cada ciclo é criada um Delegate (System.Action) que irá ser chamado e isso irá aumentar o tempo de execução.
É por isto muito importante definir bem onde utilizar, desenhar a aplicação de modo a poder incluir a utilização de Parallel Extensions, neste caso as Task Parallel Library (TPL), e testar sempre (analisando os resultados)!
A evolução dos CPU’s deixou, nos últimos anos, de estar relacionada com a velocidade do CPU, mas sim com o número de núcleos que este tem. Estes núcleos (cores) permitem executar diversos processos em simultâneo, melhorando a utilização e performance das aplicações, sendo para isso necessário que elas estejam desenhadas e preparadas para o fazer.
O Visual Studio 2010 e a .NET Framework 4.0 trazem um conjunto de melhorias com o objectivo de tirar partido do hardware, nomeadamente melhorias no namespace System.Theading, a introdução das Parallel Extensions, que são um conjunto de classes que permitem implementar a programação de tarefas em paralelo e a Unified Cancellation Model, que é, de uma forma sucinta, uma simplificação do processo de cancelamento de diversas threads usando um só comando.

São também novidade duas ferramentas de diagnóstico: Parallel Stacks Window e Parallel Tasks Window.
Sobre as Parallel Extensions, as principais novidades são:
O Visual Studio 2010 e a plataforma .NET Framework 4.0 vêm assim melhorar e simplificar o o processo de criação de threads, substituindo de certa forma, com as Parallel Extensions, a classe System.Theading.Thead (embora possível de utilizar).