Dataflow Programming com C# – Criando Variáveis e Operações

Standard

Conhecemos muito bem a tradicional programação imperativa, onde o estado de nossas variáveis são mutáveis e existe uma sequência pré-definida de comandos para que sejam executados. Já o paradigma de Dataflow (Fluxo de Dados) Programming segue um raciocínio diferente, do tipo “faça apenas quando receber algo”, um exemplo bem simples de como isso funciona: Imagine uma fábrica de roupas com seus dez funcionários, porém, para que eles comecem a trabalhar, primeiro devem receber os tecidos, ou seja, na programação com fluxo de dados a execução do programa depende dos dados de entrada.

A partir do .NET framework 4.5 já é possível utilizar os recursos da programação com fluxo de dados nativamente, utilizando para tanto a biblioteca System.Threading.Tasks.Dataflow. Porém, meu objetivo neste post é outro Tongue out, irei demonstrar como podemos criar variáveis e realizar operações com fluxo de dados em versões anteriores do framework, para ser mais direto irei utilizar a versão 4.0 com um recurso chamado TPL (Task Parallel Library) para programação paralela.

Criando Variáveis

O conceito de variáveis é um dos mais importantes quando trabalhamos com fluxo de dados. Basicamente as variaveis podem possuir dois estados: Iniciada (quando um valor é atribuído) e Não Iniciada(quando nenhum valor foi atribuido ainda). Sempre que tentar ler o valor de uma variavel Não Iniciada o mesmo ficará bloqueado, até que a mesma seja Iniciada. As variáveis de fluxo de dados podem ser iniciadas apenas uma vez (inicialização essa que é realizada efetuando um Bind na variável). Caso tente iniciar novamente a variável, será gerado um erro.

Veja como podemos criar uma classe (DataflowVar) para trabalharmos com variáveis:

public class DataflowVar<T>
{
    private volatile bool iniciada = false;
    private volatile object valor;
    private readonly object sincronizaBloqueio = new object();

    private T Valor
    {
        get
        {
            if (!iniciada)
            {
                lock (sincronizaBloqueio)
                {
                    while (!iniciada)
                        Monitor.Wait(sincronizaBloqueio);
                }
            }
            return (T)valor;
        }
        set
        {
            lock (sincronizaBloqueio)
            {
                if (iniciada)
                    throw new System.InvalidOperationException("Uma variável de Fluxo de Dados pode 

ser definida apenas uma vez.");
                else
                {
                    this.valor = value;
                    iniciada = true;
                    Monitor.PulseAll(sincronizaBloqueio);
                }
            }
        }
    }

    /// <summary>
    /// Atribui um valor do tipo T para a variável
    /// </summary>
    /// <param name="novoValor">Novo valor</param>
    public void Bind(T novoValor)
    {
        this.Valor = novoValor;
    }
}

Observe, definimos como volatile as propriedades iniciada e valor, pois temos que garantir que estas propriedades possam ser modificadas por fatores externos e que independentemente do processo que esteja acessando a propriedade, será pego o valor atual da propriedade. Utilizamos também os métodos Monitor.Wait e Monitor.PulseAll para garantir o bloqueio do objeto atual. Legal né?

Vejamos agora um exemplo simples de como podemos utiliza-lo:

var valorHora = new DataflowVar<decimal>();
var horasTrabalhadas = new DataflowVar<int>();

valorHora.Bind(300);
horasTrabalhadas.Bind(168);

Console.WriteLine(Convert.ToDecimal(valorHora) * Convert.ToInt32(horasTrabalhadas));

Deu para entender? Bem tranquilo né?

 

Criando Operações

Para criarmos uma operações iremos utilizar os recursos da biblioteca TPL (Task Parallel Library), veja:

var valorHora = new DataflowVar<decimal>();
var horasTrabalhadas = new DataflowVar<int>();

var valorTotal = new DataflowVar<decimal>();

Task.Factory.StartNew(() =>
{
    valorTotal.Bind(Convert.ToDecimal(valorHora) * Convert.ToInt32(horasTrabalhadas));
});

valorHora.Bind(300);
horasTrabalhadas.Bind(168);

Console.WriteLine(valorTotal);

Veja que o valor total é calculado apenas quando os valores de entrada são setados nas variáveis valorHora e horasTrabalhadas. Porém, observe que temos que efetuar duas conversões para obter o valor total:

valorTotal.Bind(Convert.ToDecimal(valorHora) * Convert.ToInt32(horasTrabalhadas));

Que tal melhorarmos isso? podemos criar um conversor de operações na classe DataflowVar:

public static implicit operator T(DataflowVar<T> var)
{
     return var.Valor;
}

Agora sim ficou mais limpo, veja:

valorTotal.Bind(valorHora * horasTrabalhadas);

Muito legal isso, né?

Abraços.

 

Leave a Reply

Your email address will not be published. Required fields are marked *