Eu ainda estou escrevendo esse documento. Agradeço sua paciência :)
Imagine que temos uma função que levará muito tempo para ser executada:
function facaAlgo: string;
begin
sleep(10000); // imagine um processo longo aqui (10s)
result := 'Deu certo!';
end
Essa função pode ser colocada dentro de um IFuture<string> para que possa rodar em paralelo:
function facaAlgo: string;
begin
xFuture = TTask.Future<string>(
function: string
begin
sleep(10000); // imagine um processo longo aqui
result := 'Deu certo!';
end
);
end
Mas e agora, como esperar pelo IFuture? Se usar o método Wait(), a thread principal ficará travada. Uma solução é colocar ele dentro de uma TTask ou TThread e deixar que elas invoquem um evento da thread principal:
function facaAlgoAssincrono: string;
begin
xFuture = TTask.Future<string>(
function: string
begin
sleep(10000); // imagine um processo longo aqui
result := 'Deu certo!';
end
);
TTask.Run(
procedure
begin
xResultado := xFuture.Value; // bloqueia aqui até ter o resultado
TThread.Queue(
nil,
procedure
begin
self.OnFacaAlgoFinished(xResultado); // invocando o evento!
end;
);
end
);
end
E pronto, facaAlgo() agora é assíncrona! Mas, há um problema nessa abordagem: o uso de eventos.
Imagina um sistema com um componente de comunicação que faz uma requisição por uma procedure mas recebe a resposta por um evento:
procedure pesquisar(const pProduto: string);
begin
ObjComunicacao.send('/pesquisa/por/produto/', pProduto);
end;
// evento onde os dados do ObjComunicacao chegam do servidor
procedure OnObjComunicacaoPublishReceived(ACabecalho, AResposta: string);
begin
// olha que eu nem coloquei um ID. Imagine como isso ficaria
// para várias requisições nesse cabeçalho, por threads diferentes!
if (ACabecalho = '/pesquisa/por/produto/') then
begin
facaAlgoComAResposta(AResposta);
end
end
...
// em um lugar qualquer do código:
pesquisar('maçãs');
queroFazerAlgoComARespostaAqui() // Isso não é possível, pois vou
// receber a resposta por um EVENTO!!!
Essa abordagem é comum no DELPHI, e o problema disso é: fluxo de código quebrado, dificuldade para depuração(se não acredita, veja um projeto gigante; imagine como seria a verificação para dezenas/centenas de cabeçalhos e como seria o acoplamento para poder executar o facaAlgoComAResposta() de cada unit que pesquisou um produto). O problema de usar eventos para esse tipo de procedimento é que eles são eventos!
Eventos são coisas como um clique no mouse; um pressionar de um botão; um timer estourando seu intervalo; um envio esporádico do servidor para o ObjComunicacao. Porém, quando um procedimento que se parece uma requisição (eu peço algo e espero uma resposta) fica refém de um evento, temos um problema de descentralização de código, quebra de fluxo e [ALTAMENTE POSSIVELMENTE] acoplamento.
Uma forma de resolver o acoplamento seria usando o padrão observer. Isso resolve bem o problema de acoplamento, mas ainda não resolve muito bem a descentralização do código e a quebra de fluxo (mesmo que, provavalmente com o observer, as coisas estejam mais juntas).
Então, como resolver esses problemas?
Veja como fica o código anterior se usarmos uma promise:
function facaAlgoAssincrono: string;
begin
xFuture = TTask.Future<string>(
function: string
begin
sleep(10000); // imagine um processo longo aqui
result := 'Deu certo!';
end
);
TTask.Run(
procedure
begin
xResultado := xFuture.Value; // bloqueia aqui até ter o resultado
TThread.Queue(
nil,
procedure
begin
self.OnFacaAlgoFinished(xResultado); // invocando o evento!
end;
);
end
);
end
function facaAlgo: string;
var
xPromise: IPromise<string>;
begin
xFuture = TTask.Future<string>(
function: string
begin
sleep(10000); // imagine um processo longo aqui
result := 'Deu certo!';
end
);
xPromise := TPromise<string>.Create(xFuture);
xPromise.next(
procedure (const pResultado: string)
begin
self.OnFacaAlgoFinished(xResultado); // invocando o evento!
end
);
end
Se isso não é bonito, eu não sei o que é!
Agora, imagine que o nosso objeto de comunicação (ObjComunicacao) retornasse uma promise quando o método pesquisar fosse invocado:
procedure pesquisar(const pProduto: string);
begin
ObjComunicacao.send('/pesquisa/por/produto/', pProduto);
end;
// evento onde os dados do ObjComunicacao chegam do servidor
procedure OnObjComunicacaoPublishReceived(ACabecalho, AResposta: string);
begin
if (ACabecalho = '/pesquisa/por/produto/') then
begin
facaAlgoComAResposta(AResposta);
end
end
...
// em um lugar qualquer do código:
pesquisar('maçãs');
queroFazerAlgoComARespostaAqui() // Isso não é possível, pois vou
// receber a resposta por um EVENTO!!!
function pesquisar(const pProduto: string): IPromise<string>;
begin
// esse camarada aqui retorna uma IPromise<string> ;)
result := ObjComunicacao.send('/pesquisa/por/produto/', pProduto);
end;
...
// em um lugar qualquer do código:
pesquisar('maçãs')
.next(
procedure (const pResposta: string)
begin
facaAlgoComAResposta(pResposta);
end;
); //agora eu posso fazer algo com a resposta aqui! :))
Não é lindo?! Código centralizado, desacoplado e de fácil depuração!