Você está na página 1de 6

[Delphi] Utilizando o mecanismo de

processamento paralelo
17/04/2017 por Andr L. Celestino8 Comentrios

Fala, galera!
Acredito que muitos de vocs j tenham usado, estudado ou ao menos ouvido falar de
Multithreading do Delphi. As bibliotecas deste recurso esto presentes desde a verso XE7,
porm, por serem relativamente recentes, s vezes esquecemos de sua existncia. O objetivo
deste artigo apresentar um cenrio no qual o uso de Multithreading pode trazer uma grande
vantagem em relao ao tempo de resposta de uma aplicao.

Em maro deste ano, apliquei um treinamento sobre Delphi Seattle na DB1 Global Software,
empresa em que eu trabalho, para apresentar os recursos das verses mais recentes do Delphi,
entre eles, claro, a biblioteca de Multithreading. Com esse mecanismo, podemos distribuir
processamentos complexos em threads distintas, executando-as ao mesmo tempo. Por
estarem em fluxos paralelos, o tempo de trmino destes processamentos evidentemente
menor do que uma abordagem sequencial (ou linear).
Muitos me questionaram sobre a aplicao de um processamento paralelo em um cenrio
mais prximo da realidade, portanto, dediquei os ltimos dias para elaborar este artigo.
Considere a emisso de um relatrio de pedido que possui as seguintes sees:

Dados do cliente (nome, endereo, CPF);


Dados do pedido (data, total, forma de pagamento);
Dados dos itens do pedido (produto, descrio, quantidade).

Para que a emisso seja realizada, necessrio consultar os dados dessas trs sees de forma
separada. Como exemplo, considere que cada consulta demore aproximadamente os tempos
abaixo:

Dados do cliente: 2 segundos


Dados do pedido: 3 segundos
Dados dos itens do pedido: 4 segundos
Habitualmente, a codificao para a emisso deste relatrio poderia ser:

ConsultarDadosCliente;

ConsultarDadosPedido;

ConsultarDadosItensPedido;

Para simular o tempo dispendido com essa operao, programei um Sleep() dentro de cada
mtodo com a respectiva durao:

procedure ConsultarDadosCliente;

begin

Sleep(2000);

end;

procedure ConsultarDadosPedido;

begin

Sleep(3000);

end;

procedure ConsultarDadosItensPedido;

begin

Sleep(4000);

end;

Em seguida, adicionei tambm duas variveis para calcular o tempo gasto e uma mensagem
para exibi-lo ao trmino das consultas:

procedure EmitirRelatorio;

var

Inicio: TDateTime;

Fim: TDateTime;

begin

Inicio := Now;

ConsultarDadosCliente;

ConsultarDadosItensPedido;

ConsultarDadosPedido;
Fim := Now;

ShowMessage(Format('Consultas realizadas em %s segundos.',

[FormatDateTime('ss', Fim - Inicio)]));

end;

Ao executar o mtodo de emisso, receberemos a mensagem:

O propsito deste artigo mostrar que podemos melhorar isso. Com o recurso de
processamento paralelo, podemos distribuir cada consulta em uma thread e execut-las
simultaneamente. Para isso, trabalharemos com a classe TTask, da
unit System.Threading, utilizando a seguinte sintaxe:

uses

System.Threading;

{ ... }

var

Task: ITask;

begin

Task := TTask.Create({mtodo});

Task.Start;

end;

Basta ento criar uma Task para cada consulta e disparar todas elas?
Sim, mas h uma condio que exige a nossa ateno. Voc deve ter notado que cada consulta
tem uma durao diferente. Isso significa que, se executarmos todas elas em threads,
possvel que as consultas mais demoradas no sejam finalizadas a tempo. Caso isso ocorra,
os dados dessas sees no sero exibidos no relatrio.
Para evitar esse comportamento, criaremos um array de ITask e, ao final, utilizaremos um
mtodo chamado WaitForAll para solicitar que o processamento principal s prossiga
quando todas as threads forem concludas. Confira a codificao:
procedure EmitirRelatorio;

var

Tasks: array [0..2] of ITask;

Inicio: TDateTime;

Fim: TDateTime;

begin

Inicio := Now;

Tasks[0] := TTask.Create(ConsultarDadosCliente);

Tasks[0].Start;

Tasks[1] := TTask.Create(ConsultarDadosPedido);

Tasks[1].Start;

Tasks[2] := TTask.Create(ConsultarDadosItensPedido);

Tasks[2].Start;

TTask.WaitForAll(Tasks);

Fim := Now;

ShowMessage(Format('Consultas realizadas em %s segundos.',

[FormatDateTime('ss', Fim - Inicio)]));

end;

Agora, ao executar o mesmo mtodo de emisso, este o resultado de tempo total:

Concluindo, com o processamento em paralelo, reduzimos a emisso do relatrio para metade


do tempo!
Andr, voc poderia citar outros exemplos?
Sim!
Um fechamento de caixa outro cenrio bem comum no qual o Multithreading pode ser
aplicado, j que consiste em uma srie de operaes, como conferncia de valores, clculo de
entradas e sadas, gerao de saldos e at a produo de grficos. Mesmo assim, para dar
continuidade sobre este recurso, vou apresentar mais um cenrio. Imagine uma rotina de
processamento de arquivos em lote atravs da seguinte codificao:

var

ListaArquivos: TStringList;

i: integer;

begin

ListaArquivos := TStringList.Create;

ListaArquivos.Text := CarregarArquivos;

for i := 0 to Pred(ListaArquivos.Count) do

begin

ProcessarArquivo(ListaArquivos[i]);

end;

ListaArquivos.Free;

end;

Observamos, claro, que um arquivo ser processado por vez, j que o loop iterativo e
sequencial. Esse mesmo procedimento tambm pode ser distribudo em threads paralelas, no
entanto, para este caso, no utilizamos o TTask. Devemos utilizar um
comando chamado TParallel.For, contida na mesma biblioteca. Alm disso, como
haver um acesso concorrente ao objeto ListaArquivos, necessrio utilizar um
mecanismo de semforo, representado pelo mtodo TThread.Queue no cdigo abaixo:

var

ListaArquivos: TStringList;

begin

ListaArquivos := TStringList.Create;

ListaArquivos.Text := CarregarArquivos;

TParallel.For(0, Pred(ListaArquivos.Count),
procedure (i: integer)

begin

TThread.Queue(TThread.CurrentThread,

procedure

begin

ProcessarArquivo(ListaArquivos[i]);

end)

end);

end;

Explicando: ao invs de usar um nico fluxo de processamento, o TParallel.For


distribuir as iteraes em threads, ou seja, o loop ser dividido em fluxos paralelos,
reduzindo (bastante!) o tempo.

Fico por aqui, pessoal! Espero que esse artigo incentive a utilizao dessa poderosa biblioteca
do Delphi.
Um grande abrao!

Você também pode gostar