Fala Galera,
Hoje vamos abordar dois conceitos muitos utilizados na plataforma .NET, a programação em paralelo e em tarefas.
Task Parallel Library (TPL) é a biblioteca responsável por podermos usar os conceitos deTask e de Parallel no .NET
Essa biblioteca contém um conjunto de API’s (Application Programming Interfaces) para simplificar o processo de adicionar paralelismo e concorrência em uma aplicação.
O que é Task Parallel Library
Como foi dito, a TPL é uma biblioteca que contém um conjunto de APIs públicas e essasAPIs estão localizados no namespace System.Threading e System.Threading.Tasks.
E como funciona a TPL? Para se executar uma programação Paralela ou em Tarefas internamente a TPL funciona da seguinte maneira, quando criamos uma tarefa por exemplo a TPL dimensiona o grau de simultaneidade dinamicamente para usar todos os processadores disponíveis de forma mais eficiente. Além disso, com a TPL podemos executar particionamento de tarefas, suporte a cancelamento e o gerenciamento de estado.
Como funciona um aplicação multi thread
Quando nós criamos uma aplicação multi thread, o sistema operacional associa cada thread a um core do seu computador porém na realidade algumas threads podem ser executadas em core distintos e algumas vezes não.
Então para se resolver o problema acima foi criado um mecanismo chamado Task. Uma tarefa nada mais é que uma operação assíncrona que será gerenciado pela TPL utilizando os recursos do ThreadPool. Ao gerenciar as tarefas, a TPL consegue alocar todas as threads para ser executadas em todos os core disponíveis.
Utilizando as tarefas obtemos os benefícios da programação multi thread de forma mais simples com garantia de isolamento e gerenciamento de concorrência.
Data Parallelism
Paralelismo de dados é um conceito aonde temos cenários em que a mesma operação é executada simultaneamente ou seja em paralelo em elementos de uma fonte dados.
No paralelismo de dados, a fonte de dados é particionada em diversas thread aonde cada thread podem operar simultaneamente em diferentes segmentos do código.
A TPL dá suporte a programação ao paralelismo utilizando a classeSystem.Threading.Tasks.Parallel. Essa classe fornece métodos estáticos para se criar loop em paralelo.
Por baixo dos panos quando um loop em paralelo é executado a TPL usa uma classe chamada Partioner<T> para que o loop funcione simultaneamente em várias partes, assim o TaskScheduler particiona as tarefas com base na carga de trabalho e nos recursos disponíveis do sistema operacional. Algumas vezes é necessário redistribuir a carga entre várias threads e processadores se a carga de trabalho ficar desbalanceada.
Veja o exemplo abaixo:
// For tradicional for (int i = 0; i < 100000; i++) Console.WriteLine($"Exibindo o loop pelo indice {i}"); // Mesmo for executado em paralelo System.Threading.Tasks.Parallel.For(0, 100000, (i) => { Console.WriteLine($"Exibindo o loop pelo indice {i}"); });
Task Parallelism
Uma tarefa nada mais é que um operação assíncrona. Uma tarefa é similar a uma thread porém com um nível maior de abstração. Podemos criar uma ou mais tarefas e essas tarefas serão executadas de formas independentes porém sua execução será simultânea.
O uso de tarefas fornecem dois principais benefícios:
- Uso mais eficiente e mais dimensionável de recursos do sistema.
- Maior controle programático sobre uma tarefa do que uma simples thread
Todas as tarefas são enfileiradas no ThreadPool, ele foi aprimorado com algoritmos que determinam e ajustam o números de threads para fornecer um balanceamento de carga a maximizar o uso dos recursos do sistema operacional.
Tarefas e estruturas criadas em torno delas fornecem um conjunto de APIs que oferecem suporte a cancelamento, continuações, tratamento de exceção robusto, status detalhado de uma tarefa, agendamento e entre outros.
Uma tarefa que não retorna valor, ela é representada pelo classeSystem.Threading.Tasks.Task. Uma tarefa que retorna valor é representada pela classeSystem.Threading.Tasks.Task<TResult>. Uma tarefa lida com todos os detalhes de infraestrutura e fornece métodos e propriedades que são acessíveis durante a vida útil da tarefa.
Veja o exemplo abaixo:
static void Main(string[] args) { var task = Task.Factory.StartNew(() => { Print(); }); task.Wait(); Console.Read(); } public static void Print() { Console.WriteLine("Comando executado pela task"); }
Podemos também aguardar a execução de todas as tarefas criadas e assim continuar nosso programa.
Veja o exemplo abaixo:
Task[] tasks = new Task[3] { Task.Factory.StartNew(() => MethodA()), Task.Factory.StartNew(() => MethodB()), Task.Factory.StartNew(() => MethodC()) }; Task.WaitAll(tasks);
Com a TPL podemos fazer coisas incríveis de forma simples, todo o trabalho de gerenciamento de execução e alocação de recursos ficam a cargo da TPL tornando nossa vida muito mais simples. Ao usar a TPL, você pode maximizar o desempenho do seu código enquanto se concentra no trabalho para o qual seu programa foi criado para realizar.
O que acharam ? Não deixem de comentar!
Abs e até a próxima.
Olá Rafael, parabéns pelo artigo!
Por favor, veja se pode me tirar uma dúvida.
Digamos que eu queira criar uma tarefa de cópia ou exclusão de arquivos e isso não é prioridade de processamento, mas também não quero incluir gerenciadores de tarefas, como o Hangfire, para realizar essa operação. Existe uma forma de disparar uma tarefa dizendo que ela é de baixa prioridade, talvez setando ela para trabalhar em somente um núcleo de processador?
Como você acha que eu poderia fazer isso de uma forma elegante e funcional?
Grato!
Ola, obrigado pela visita,
Tem como sim, basta no método dentro da task setar ela como baixa prioridade.
Segue um exemplo:
Task.Factory.StartNew(StartTaskMethod);
void StartTaskMethod()
{
try
{
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
DoSomething();
}
finally
{
Thread.CurrentThread.Priority = ThreadPriority.Normal;
}
}
Rafael, parabéns pelo tópico. Você poderia tirar uma dúvida? Estou usando threads aqui no meu projeto, você teria algum exemplo usando threads e concorrência somente para uma explicação de como funciona?
Opa Rpbson, muito obrigado pela ajuda.
Existe esse algorítimo que pode fazer vc entender como funciona a parte de concorrência
public class Coffee
{
private object coffeeLock = new object();
int stock;
public Coffee(int initialStock)
{
stock = initialStock;
}
public bool MakeCoffees(int coffeesRequired)
{
// This condition will never be true unless you comment out the lock statement.
if (stock < 0) { throw new Exception("Stock cannot be negative!"); } //lock (coffeeLock) { // Check that there is sufficient stock to fulfil the order. if (stock >= coffeesRequired)
{
// Introduce a delay to make thread contention more likely.
Thread.Sleep(500);
Console.WriteLine(String.Format("Stock level before making coffee: {0}", stock));
Console.WriteLine("Making coffee...");
stock = stock - coffeesRequired;
Console.WriteLine(String.Format("Stock level after making coffee: {0}", stock));
return true;
}
else
{
Console.WriteLine("Insufficient stock to make coffee");
return false;
}
}
}
}
static void Main(string[] args)
{
// Create a Coffee instance with enough stock to make 1000 coffees.
Coffee coffee = new Coffee(1000);
Random r = new Random();
// Create 100 iterations of a parallel for loop.
Parallel.For(0, 100, index =>
{
// Place an order for a random number of coffees.
coffee.MakeCoffees(r.Next(1, 100));
});
Console.ReadLine();
}
Abs,