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.

Execução dos métodos 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.

Execução do FOR em paralelo

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.