Execução de tarefas em paralelo no .NET 4.0

Veja neste artigo como utilizar algumas classes dos namespaces System.Threading e System.Threading.Tasks para executar tarefas em paralelo em aplicações .NET.

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: ", 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: ", 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: ", 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: ", 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: ", 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: ", 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 ", t1.Result); Console.WriteLine("String original ", t2.Result); Console.WriteLine("Quantidade de itens no Array ", 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 ", pt1.Result); Console.WriteLine("String original ", pt2.Result); Console.WriteLine("Quantidade de itens no Array ", 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 = , thread = ", i, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(10); } Console.WriteLine("Usando Parallel.For"); Parallel.For(0, 10, i => { Console.WriteLine("i = , thread = ", 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.

Artigos relacionados