Quando se trabalha com processos paralelos o isolamento de memória com acesso comum a processos diferentes é fundamental.

Uma opção é implementar utilizando  TThreadList que mantem o controle da lista bloqueando ou liberando quando precisa acessar a área de armazenamento sem especialidades do TStringList .

De outro lado o TStringList não é threadsafe o que motiva a reescrever as funcionalidades para compartilhamento da lista entre os processos…   Ver Classes TThreadSafe….

[code lang=”pascal”]
TThreadSafeStringList
// public
procedure Clear;
function Count: integer;
function IndexOf(AText: string): integer;
function IndexOfName(AText: string): integer;
procedure Add(AText: string; ADupl: boolean = true);
procedure Delete(AIndex: integer);
procedure Remove(AText: string);
function LockList: TStringList;
procedure UnlockList; inline;
property Items[AIndex: integer]: string read Getitems write Setitems;
property Delimiter: Char read GetDelimiter write SetDelimiter;
property DelimitedText: string read GetDelimitedText write SetDelimitedText;
function Text: string;
property CommaText: string read GetCommaText write SetCommaText;
property QuoteChar: Char read GetQuoteChar write SetQuoteChar;
procedure Assing(AStrings: TStrings);
procedure AssingTo(AStrings: TStrings);
procedure AddTo(AStrings: TStrings);
property Values[AName: string]: String read GetValues write SetValues;
property Names[AIndex: integer]: String read GetNames write SetNames;
[/code]

Exemplo de como utilizar as lista compartilhando em Threads diferentes mantendo o controle do acesso a lista… [Codigo]

[code lang=”pascal”]
{$R *.fmx}

procedure TForm2.Button1Click(Sender: TObject);
var
x: integer;
begin
strList.Clear;
// Thread 1
tthread.CreateAnonymousThread(
procedure
var
x: integer;
begin
for x := 0 to random(1000) do
begin
strList.Add(‘X’ + intToStr(x));
tthread.Sleep(random(10));
end;
strList.Add(‘X-FIM’);

tthread.Queue(nil,
procedure
begin
strList.AssingTo(Memo1.lines);
end);

end).start;

// Thread 2
tthread.CreateAnonymousThread(
procedure
var
x: integer;
begin
for x := 0 to random(1000) do
begin
strList.Add(‘Z’ + intToStr(x));
tthread.Sleep(random(10));
end;
strList.Add(‘Z-FIM’);

tthread.Queue(nil,
procedure
begin
strList.AssingTo(Memo1.lines);
end);

end).start;

end;

procedure TForm2.FormCreate(Sender: TObject);
begin
strList := TThreadSafeStringList.create;

end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
strList.free;
end;

[/code]

Um boa dor de cabeça é resolver exceções em TThread, TTasks….

Base de Conhecimento
Como pré-requisito é preciso ter em mente (“recomenda-se”) não existir uma exceção não tratada dentro de uma TThread – então todos os processo deveriam tratar as suas exceções internamente com um Try/Exception.

[code]
Try
código com erro…
Except
on e:exception do
fazer algo…
end;
[/code]

 

No framework parte do “post” LogEvents é possível prever o tratamento de exception acrescentando o método RUN…

[code]
procedure TLogListItems.Run(proc: TProc);
begin
TThread.CreateAnonymousThread(
procedure
begin
try
proc;
except
on e: exception do
LogEvents.DoErro(nil, 0, e.message);
end;
end).start;
end;
[/code]

Com método RUN recebendo um ANONIMOUS Procedure – permite que a aplicação faça assim:

[code]
// usado metodo ANONIMOUS para tratar exception internamente
LogEvents.Run(
procedure begin
// código com potencial de exceção

// força um exception
raise Exception.Create(‘Error Message’);
end);

[/code]

Como mostrar o erro ao usuário

O Framework “LogEvents” possui um método de inicialização “register” que o formulário irá se inscrever para receber mensagens… e outro para retirar a inscrição “unregister“.

LogEvents.register(self, DoErro, 0); // recebe os registro de ERROS

onde:

  • self é o formulário que irá recebe mensagens…
  • DoErro é o método do furmulario…
  • e o terceiro parâmetro é um identificador que qualifica o tipo de mensagem que irá receber

O mesmo formulário pode subscrever a receber mais de um tipo de mensagem;

[code]

LogEvents.register(self, DoErro, 0); // recebe os registro de ERROS
LogEvents.register(self, DoSucesso, 1); // registra para receber os sucessos

[/code]

Para não receber mensagens – em geral quando o formulário fecha “close” usar: LogEvents.unregister(self);

Enviando mensagem para o formulário

O método genérico “Log” permite enviar uma mensagem para o identificador “0” (usado no register):

LogEvents.Log(‘Minha mensagem a ser mostrada’);

Para enviar uma mensagem com um identificador específico:

LogEvents.DoErro(nil, 1, ‘LOG…..’);  // register = 1

Código de Exemplo: LogEvents – Mostrando Erros ao usuário

 

 

[usa LogEvents]
Tenho uma quantidade de produtos relativamente grande que requer processamento de custos de produção envolvendo custo de matérias primas, mão-de-obra e outros custos vinculado a célula de produção.

A modelagem prevê que uma ficha de produção pode conter outras fichas formando uma lista de dependências dos processos o que gera processamento recursivo de dependências.

Como se pode imaginar, não é um processamento sequenciado tão simples e pode ser demorado em face a profundidade da arvore de dependência que um produto pode exigir.

Então repensando os processos, o desafio passou exigir processamento em paralelo das fichas de tal forma que fosse possível processar uma quantidade de produtos ao mesmo tempo e aproveitando melhor os recursos da máquina;

Neste cenário, saber qual o estágio de processamento de cada ficha e o onde se encontra o cálculo passou a ser requisito de interação com usuário;

Para executar vamos utilizar da biblioteca de processamento em paralelo do Delphi (introduzido no XE7, no exemplo usamos Berlin).

Passos:

  • Isolar as conexões de banco de dados para trata-las individualmente por Task;
  • Criar infraestrutura de comunicação entre o processamento e feedback com usuário;
  • Tratar a sincronização de informações geradas pelas várias TTasks em andamento informando a janela de progresso do usuário;
imagem_janela
Tendo em mente que o controle possa ser utilizado em outras aplicações, o uso de um procedimento ANONIMOUS me parece bastante resistente a diversidade de códigos a que poderá vir a ser utilizado.
Veja como ficou o exemplo de execução:

[code lang=”pascal”]

procedure TForm8.Button1Click(Sender: TObject);
var
LProgr: IProgressEvents;
i: integer;
begin
// inicializa a janela de progresso
LProgr := TProgressEvents.new;
LProgr.max := 100; // opcional: marca o número máximo itens
LProgr.MaxThreads := SpinEdit1.Value ; // indica o número máximo de threads em paralelo
LProgr.CanCancel := true; :// marca se pode cancelar a operação

for i := 1 to 100 do
begin // loop de demonstração – simulando uma lista de processos
LProgr.Text := ‘Produto: ‘ + intToStr(i); // texto livre

// onde as coisas acontecem…..
// adiciona o processo a ser executado e aponta o método anonimous as ser executado pela TTask
LProgr.add(i, ‘Produto: ‘ + intToStr(i), // processo a executar
procedure(x: integer)
var
n: integer;
msg: string;
begin
msg := ‘Produto: ‘ + intToStr(x); // processo em execução
LogEvents.DoProgress(self, 0, etStarting, msg); // notifica que o processo foi iniciado
n := Random(10000);

sleep(n);
LogEvents.DoProgress(self, 0, etWorking, msg); // notifica que esta em execução
// executa o código de calculo … aqui…
n := Random(10000);
if LProgr.Terminated then exit; // checa se o usuario cancelou a operação
sleep(n);
end);
if LProgr.Terminated then
break;
end;
LogEvents.DoProgress(self, 0, etAllFinished, ”); // sinaliza que todas os processo foram completados.
end;

[/code]

Código fonte com o Exemplo e classes que implementam a janela de monitoramento do progresso de cada thread.