No .NET 4.0, temos um novo conjunto de API para simplificar o processo de adição de paralelismo e simultaneidade nas aplicações. Este conjunto de API é chamado de "Task Parallel Library (TPL)" e situa-se nos namespaces System.Threading e System.Threading.Tasks.
A classe que vamos utilizar é a Parallel, essa classe fornece substituições paralelas para operações comuns como, for, foreach, entre outros. Adicione os seguintes namespaces:
Listagem 1: Namespaces
using System.Threading;
using System.Threading.Tasks;
Agora vamos escrever três métodos que vamos chamar simultaneamente usando o Parallel.Invoke().
Listagem 2: Método para escrever números
static void GerarNumeros()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("GerarNumeros - Numeros: {0}", i);
Thread.Sleep(1000);
}
}
Esse método vai escrever 10 números e a thread de execução vai dormir por 1 segundo para cada interação.
Listagem 3: Método para escrever caracteres
static void EscreveChar()
{
string str = "parallel";
for (int i = 0; i < str.Length; i++)
{
Console.WriteLine("EscreveChar - Char: {0}", str[i]);
Thread.Sleep(1000);
}
}
Esse método vai escrever um char de cada vez da palavra “parallel” e a thread de execução vai dormir por 1 segundo para cada interação.
Listagem 4: Método para escrever números do array
static void EscreveArray()
{
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8 };
foreach (int i in arr)
{
Console.WriteLine("EscreveArray - Array: {0}", i);
Thread.Sleep(1000);
}
}
Esse método vai escrever todos os números do array e a thread de execução vai dormir por 1 segundo para cada interação.
Listagem 5: Chamada em paralelo dos 3 métodos
static void Main(string[] args)
{
Parallel.Invoke(
new Action(GerarNumeros),
new Action(EscreveChar),
new Action(EscreveArray)
);
Console.ReadLine();
}
Agora vamos usar a classe Parallel, usando o método estático Invoke, vamos criar três Action que executarão os métodos criados simultaneamente.
Observe como esses métodos estão sendo chamados em paralelo.

Figura 1: Execução dos métodos em paralelo
Usamos o Parallel.Invoke() para chamar os métodos que não retornam um valor. Para os métodos que retornam um valor, é necessário usar a classe Task. Vamos fazer um exemplo agora com a classe Task.
Listagem 6: Método para escrever números e retorna o ultimo número gerado
static int ReturnGerarNumeros()
{
int i;
for (i = 0; i < 10; i++)
{
Console.WriteLine("GerarNumeros - Numeros: {0}", i);
Thread.Sleep(1000);
}
return i;
}
Listagem 7: Método para escrever os caracteres de uma string e retornar a string original
static string ReturnEscreveChar()
{
string str = "parallel";
for (int i = 0; i < str.Length; i++)
{
Console.WriteLine("EscreveChar - Char: {0}", str[i]);
Thread.Sleep(1000);
}
return str;
}
Listagem 8: Método para escrever os números de um Array e retorna a quantidade de números do array
static int ReturnEscreveArray()
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 };
foreach (int i in arr)
{
Console.WriteLine("EscreveArray - Array: {0}", i);
Thread.Sleep(1000);
}
return arr.Count();
}
Para chamar esses três métodos simultaneamente e aceitar os valores de retorno, podemos adotar duas abordagens.
Abordagem 1: usar o construtor da classe Task para inicializar a tarefa, mas executá-la mais tarde.
Listagem 9: Usando a classe Task
Task<int> t1 = new Task<int>(ReturnGerarNumeros);
Task<string> t2 = new Task<string>(ReturnEscreveChar);
Task<int> t3 = new Task<int>(ReturnEscreveArray);
// Inicia as tarefas
t1.Start();
t2.Start();
t3.Start();
//Escreve os valores retornados
Console.WriteLine("Numeros gerados ate {0}", t1.Result);
Console.WriteLine("String original {0}", t2.Result);
Console.WriteLine("Quantidade de itens no Array {0}", t3.Result);
Abordagem 2: podemos salvar um passo e também melhorar o desempenho de tarefas usando Task<>.Factory.StartNew() para inicializar e executar a tarefa ao mesmo tempo.
Listagem 10: Usando a classe Task com melhor desempenho
// Cria e executa as Tasks
var pt1 = Task<int>.Factory.StartNew(() => ReturnGerarNumeros());
var pt2 = Task<string>.Factory.StartNew(() => ReturnEscreveChar());
var pt3 = Task<int>.Factory.StartNew(() => ReturnEscreveArray());
//Escreve os valores retornados
Console.WriteLine("Numeros gerados ate {0}", pt1.Result);
Console.WriteLine("String original {0}", pt2.Result);
Console.WriteLine("Quantidade de itens no Array {0}", pt3.Result);
Execute o aplicativo e observe como esses métodos estão sendo chamados ao mesmo tempo e o valor de retorno é impresso usando a propriedade Task.Result.
Agora vamos ver um exemplo da utilização do FOR e o Parallel.For, o for é executado somente em uma thread. No entanto, o Parallel.For utiliza varias threads, além disso a ordem de interação na versão em paralelo não é necessariamente a ordem comum, como vamos ver no exemplo.
Listagem 11: Usando FOR e Parallel.For
Console.WriteLine("Usando C# For Loop");
for (int i = 0; i <= 10; i++)
{
Console.WriteLine("i = {0}, thread = {1}",
i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(10);
}
Console.WriteLine("Usando Parallel.For");
Parallel.For(0, 10, i =>
{
Console.WriteLine("i = {0}, thread = {1}", i,
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(10);
});
Como você pode ver, o método é definido como Parallel.For(Int32, Int32, Action (Of Int32)). O primeiro parâmetro é o índice de início, o segundo parâmetro é o índice final e o terceiro é o delegate <int> que é invocado para cada interação.
Após a execução desse código, você poderá notar que no FOR os números são impressos na ordem de 0 a 10 e todos são executados pela mesma THREAD (1), e no Parallel.For não é executado na ordem porque varias Threads estão executando a interação.

Figura 2: Execução do FOR em paralelo
Observação: para a utilização do Parallel.For os elementos da interação devem poder ser processados de forma independente.
Então finalizamos aqui este artigo, cujo objetivo é mostrar os métodos disponíveis para processamento paralelo. Espero ter ajudado a todos, abraços e até a próxima oportunidade.
Não deixe de VOTAR e deixar o seu feedback, obrigado.