Fala ai Radizeiro e Radizeira, tudo bem com você?

Ao longo do desenvolvimento de um software, passamos por diversas operações.

Um projeto até sua disponibilização para um cliente passa por diversas coisas, desde o levantamento de requisitos, desenvolvimento, documentação, testes unitários, integração contínua, até chegar ao cliente.

Mas isso pode parecer um universo muito diferente ou igual ao seu.

Mesmo que executamos diversos testes, utilizamos ferramentas, e até mesmo temos um profissional voltado para realizar as operações de testes, ainda assim nos deparamos com travamentos e erros no software.

Em nossa série sobre Paralelismo, estou mostrando para vocês todas as funcionalidades existes e como você pode resolver muitos desses problemas utilizando Threads.

E no post de hoje irei apresentar para você o IFuture, mais uma interface disponível nesta biblioteca.

Vimos em posts anteriores sobre o TTask, o ITask, de como eles funcionam, mesmo de forma bem embrionária.

Mas tenho certeza que com o conhecimento aplicado aqui nessa nossa série e um pouco de criatividade, podemos melhorar muito os nossos softwares.

Antes que possamos desenvolver diversas coisas mais elaboradas, utilizando essa biblioteca, precisamos conhecer os recursos que temos disponíveis.

Mas chega de muito papo, vamos ao que interessa.

Temos um novo projeto, em Firemonkey mesmo.

Se essa poderia ser uma dúvida sua, nesse post já estou respondendo, sim é possível utilizar a biblioteca de paralelismo dentro do Firemonkey.

Dentro do botão Normal, iremos implementar uma rotina a qual normalmente muitos de nós fazíamos, ou ainda fazemos.

E nessa implementação iremos processar três operações simultaneamente.

procedure TForm1.Button1Click(Sender: TObject);
var
    a,b,c:string;
    tempo:cardinal;
begin
    tempo:=gettickcount;

    Sleep(5000);
    a:= random(100).ToString;

    Sleep(3000);
    b:= random(100).ToString;

    Sleep(2000);</span>
    c:= random(100).ToString;

    tempo:=gettickcount-tempo;
    label1.text:= 'tempo gasto: '+inttostr(tempo)+'ms valor: '+a+'-'+b+'-'+c;

Observe que no código acima eu tenho 3 operações a serem executadas, coisas bem simples não é verdade?

Isso é apenas um exemplo, espero que ninguém faça algo do tipo….rsrs

Cada operação aguarda um tempo para executar um random para as variáveis.

Temos uma variável chamada tempo, onde ela inicia com um tempo e no final eu pego o tempo atual menos o tempo iniciado, isso é somente para que possamos saber quanto tempo durou a  operação.

Dentro do label eu passei as variáveis.

Vamos ver essa nossa aplicação funcionando

Viu como trava a aplicação?

Enquanto está processando as operações, não conseguimos fazer nada.

Somente logo após os 10 segundos obtivemos o resultado.

Executamos essa operação sem o paralelismo, somente para que você possa se situar.

O IFuture 

Uma diferença entre o IFuture e o TTask/ITask, é que o TTask/ITask é como se fosse uma procedure, onde você executa uma ação sem retorno, já o IFuture já permite que você tenha um retorno dessa ação.

Inclusive, ele nos permite a trabalhar com generics, para que você possa especificar qual é o tipo de retorno você irá ter.

Ele é como se fosse uma function em Thread, uma biblioteca rodando paralelamente.

Observe que executamos o código anterior sem o paralelismo e aconteceu aquele travamento chato na aplicação.

Vamos rodar agora esse mesmo exemplo em Thread com IFuture.

Lembrando que o IFuture trás o retorno para nós.

Vamos por um novo botão nesse nosso exemplo e alterar o texto para IFuture, nele iremos implementar a mesma operação feita anteriormente, mas com o IFuture.

...
public
    a : IFuture<String>;
    b : IFuture<String>;
    c : IFuture<String>;
...

Observe que foi criado as mesmas variáveis que criamos anteriormente, mas dentro de public e com o tipo IFuture, que aguarda um retorno genérico String.

Agora dentro desse botão que acabamos de adicionar no exemplo, iremos utilizar essas variáveis.

Vamos agora implementar a rotina usando o IFuture.

procedure TForm3.Button1Click(Sender: TObject);
begin
    TTask.Run(
        procedure
        var
            tempo: Cardinal;
            resultado: string;
        begin
            tempo := GetTickCount;
            a := TTask.Future<string>(
            function: string
            begin
                Sleep(5000);
                Result := Random(100).ToString;
            end);

            b := TTask.Future<string>(
            function: string
            begin
                Sleep(3000);
                Result := Random(100).ToString;
            end);

            c := TTask.Future<string>(
            function: string
            begin
                Sleep(2000);
                Result := Random(100).ToString;
            end);

            resultado := a.Value + ', ' + b.Value + ', ' + c.Value;
            tempo := GetTickCount - tempo;

            TThread.Synchronize(TThread.CurrentThread,
            procedure
            begin
                Label1.Text := 'Tempo gasto: ' + IntToStr(tempo) + 'ms ' + 'Valor: ' + resultado;
            end);
    end);
end;

Primeira coisa que foi feito, foi fazer com que essas nossas variáveis rodassem dentro de uma Task, ou seja, dentro deu mas fila separada.

Dentro dessa nossa Task, implementamos um método anônimo, que foi adicionado todo o código anteriormente implementado.

Mas se você observar temos algumas diferenças.

Lembra que comentei que o IFuture aguarda um retorno?

Então, eu defini no generic o tipo desse retorno, então simplesmente implementei um método anônimo com o retorno desse tipo.

Dentro desses métodos anônimos foi adicionado a mesma rotina feita para cada variável.

...
a := TTask.Future<string>(
function: string
begin
    Sleep(5000);
    Result := Random(100).ToString;
end);
...

Ou seja, cada um desse a,b e c, rodamos em uma Task.Future separada.

Após o término desses processos precisamos buscar os valores deles, é aí que entra o Thread.Synchronize.

...
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
    Label1.Text := 'Tempo gasto: ' + IntToStr(tempo) + 'ms ' + 'Valor: ' + resultado;
end);
...

Lembra que quando eu estou rodando dentro de uma Thread e preciso atualizar um componente visual, eu utilizo o Thread.Sincronize?

Para que pudéssemos pegar o retorno das variáveis, se você observar no código anterior, temos um bloqueio separado para isso, e logo em seguida atualizamos o Label com o Sincronize.

...
resultado := a.Value + ', ' + b.Value + ', ' + c.Value;
tempo := GetTickCount - tempo;
...

Simplesmente passei para uma variável do tipo string os valores retornados das IFuture.

Agora ao executar esse nosso exemplo ele irá rodar cada um desses processos separados e logo em seguida atualizamos o Label.

Muito legal não é?

Agora para ficar muito mais claro, vamos implementar um negócio diferente.

Nesse nosso exemplo vamos colocar um Timer.

Ai vem as facilidade e coisa legais que temos por estar usando essa biblioteca.

Dentro do Timer, já podemos fazer o seguinte.

O Timer funciona de 1 e 1 segundo, onde iremos consultar uma propriedade que temos dentro do IFuture, onde podemos consultar o status da Thread.

Dentro do Timer vamos verificar se ela foi terminada, atualizamos os demais Labels, passando o resultado das variáveis a,b e c.

procedure TForm3.Timer1Timer(Sender: TObject);
begin
    if a.Status = TTaskStatus.Completed then
        Label2.Text := 'Resultado A: ' + a.Value
    else
        Label2.Text := 'Resultado A: ---';
    if b.Status = TTaskStatus.Completed then
        Label3.Text := 'Resultado B: ' + b.Value
    else
        Label3.Text := 'Resultado B: ---';
    if c.Status = TTaskStatus.Completed then
        Label4.Text := 'Resultado C: ' + c.Value
    else
        Label4.Text := 'Resultado C: ---';
end;

Esse nosso timer estará rodando em paralelo.

A única coisa que eu tenho que fazer é desabilitar o Timer, e dentro do botão antes da TTask.Run irei verificar se o timer não está ativo, para pode ativa-lo.

...
if not Timer1.Enabled then
    Timer1.Enabled := true;
...
TTask.Run(
...

Vamos executar e ver como irá se comportar.

Viu o resultado aparecendo gradativamente?

A cada finalização da Thread ele vai alterando os valores das Labels.

Muito legal a utilização do IFuture não é?

E o mais legal, não travando a nossa aplicação.

A utilização dessa biblioteca ajuda muito nessas rotinas.

Essas funcionalizadas e entre outras você consegue encontrar dentro do CLUBE DOS PROGRAMADORES EM DELPHI.

E caso você tenha interesse de conhecer mais sobre PPL acessa o nosso portal do CLUBE DE PROGRAMADORES EM DELPHI, onde você não só terá conteúdos relacionados aos generics, mas uma quantidade enorme de conteúdos que poderá lhe ajudar muito no seu dia a dia, é uma verdadeira NETFLIX para os programadores Delphi.

CLIQUE AQUI E SAIBA MAIS SOBRE O CLUBE DOS PROGRAMADORES DELPHI