Para implementar essa solução em Delphi, eu comecei criando uma unit que centralizará o controle da sincronização. Ela terá uma variável global para representar o evento "Término das threads de Classificação" e funções para incrementar e decrementar a quantidade de Threads ativas. O objetivo é que cada nova thread de classificação que for criada incremente um contador interno e, quando essa Thread terminar, o contador seja decrementado. Quando ele atingir o valor "zero", o evento será acionado para avisar que as listas já estão ordenadas:
var _QtThreads : Integer;
_CS_QtThreads : TCriticalSection;
procedure IncQtThreads;
begin
{ Incrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;
try // Por segurança, trata exceções
Inc(_QtThreads);
finally
_CS_QtThreads.Release;
end;
end;
procedure DecQtThreads;
begin
{ Decrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;
try
Dec(_QtThreads);
{ Se Chegou a zero, significa que as threads de classificação já terminaram a execução. Então, notifica a ocorrência do evento p/ quem estiver aguardando }
if _QtThreads = 0 then
evtThreadsSort.SetEvent;
finally
_CS_QtThreads.Release;
end;
end;
_CS_QtThreads : TCriticalSection;
procedure IncQtThreads;
begin
{ Incrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;
try // Por segurança, trata exceções
Inc(_QtThreads);
finally
_CS_QtThreads.Release;
end;
end;
procedure DecQtThreads;
begin
{ Decrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;
try
Dec(_QtThreads);
{ Se Chegou a zero, significa que as threads de classificação já terminaram a execução. Então, notifica a ocorrência do evento p/ quem estiver aguardando }
if _QtThreads = 0 then
evtThreadsSort.SetEvent;
finally
_CS_QtThreads.Release;
end;
end;
A linha evtThreadsSort.SetEvent notifica que o evento esperado ocorreu. Então, na classe que representa a Thread aguardando, uso a variável do evento para esperar pela notificação:
procedure TWThrEsperaSort.Execute;
var res : TWaitResult;
begin
{ Espera no máximo 1 minuto e meio pelo evento }
res := evtThreadsSort.WaitFor(90000);
{ Se tudo correu bem, neste ponto as listas já estão ordenadas. }
var res : TWaitResult;
begin
{ Espera no máximo 1 minuto e meio pelo evento }
res := evtThreadsSort.WaitFor(90000);
{ Se tudo correu bem, neste ponto as listas já estão ordenadas. }
Este código suspende a execução da Thread enquanto aguarda. É por essa razão que eu não uso a Thread principal para aguardar; se fizesse isso, o usuário perderia a interação com o Form durante a espera, dando a impressão que o programa todo "travou". Essa Thread só continuará sua execução quando o evento esperado ocorrer ou se o tempo de espera (timeout) expirar ou ainda se houver algum erro inesperado.
Se tudo correu bem, as listas estarão classificadas no ponto do código imediatamente após a chamada ao WaitFor e o processo que depende da classificação de todas as listas pode continuar.
Eu criei uma classe de Thread para realizar a classificação de listas e, no construtor dela, faço o incremento do contador de threads ativas. O decremento desse valor é chamado na função de finalização.
Constructor TWThrSort.Create (PStatus: TWThreadStatus);
begin
inherited Create (true);
IncQtThreads;
FreeOnTerminate := true;
OnTerminate := OnThreadFinish;
{ ... }
end;
procedure TWThrSort.OnThreadFinish(Sender: TObject);
begin
{ ... }
DecQtThreads;
{ ... }
begin
inherited Create (true);
IncQtThreads;
FreeOnTerminate := true;
OnTerminate := OnThreadFinish;
{ ... }
end;
procedure TWThrSort.OnThreadFinish(Sender: TObject);
begin
{ ... }
DecQtThreads;
{ ... }
Duas coisas a observar no trecho acima. Quando chamo o construtor herdado Create, informo True no parâmetro createSuspended, o que cria a Thread em estado suspenso. Isto significa que terei que iniciá-la manualmente. O outro ponto é que ajustei a propriedade FreeOnTerminate para True, indicando que a finalização ocorrerá automaticamente quando encerrar a função Execute.
A ligação disso tudo se dá no ponto em que as Threads de classificação são instanciadas. Neste caso, inclui o código abaixo como resposta ao clique de um botão no meu Form:
{ Reseta o evento para garantir que está no estado inicial }
evtThreadsSort.ResetEvent;
{ Cria a thread de espera passando o handle desse Form }
TWThrEsperaSort.Create(Handle);
{ Cria as threads de classificação }
for i := 0 to 3 do begin
lStatus := TWThreadStatus.Create;
{ ... }
thds[i] := TWThrSort.Create(lStatus);
end;
{ sincroniza o início das threads de classificação }
for i := 0 to 3 do
thds[i].Resume;
{ ... }
evtThreadsSort.ResetEvent;
{ Cria a thread de espera passando o handle desse Form }
TWThrEsperaSort.Create(Handle);
{ Cria as threads de classificação }
for i := 0 to 3 do begin
lStatus := TWThreadStatus.Create;
{ ... }
thds[i] := TWThrSort.Create(lStatus);
end;
{ sincroniza o início das threads de classificação }
for i := 0 to 3 do
thds[i].Resume;
{ ... }
É preciso colocar a variável do Evento em seu estado inicial (ResetEvent) antes de criar a thread de espera e só então podemos criar as demais threads, dedicadas à classificação das listas.
Mas, porque iniciar as Threads manualmente neste caso? Se eu deixar a criação da Thread iniciá-la automaticamente e a primeira Thread for rápida o suficiente, ela poderia terminar antes da segunda Thread ter tempo de incrementar o contador, o que, por sua vez, acionaria o Evento antes da hora pois o contador das threads ativas voltaria ao valor zero. Ao fazer a criação e a execução em pontos distintos, o contador de threads ativas estará em seu valor máximo quando as threads se iniciarem. Assim, mesmo que a primeira Thread execute muito rápido, o contador só voltará ao valor zero quando todas terminarem, acionando o Evento no momento apropriado.
As classes TEvent e TCriticalSection encapsulam chamadas a funções da API do Windows para trabalhar com sincronização. Essas funções podem ser chamadas diretamente, se for necessário. A documentação para elas pode ser encontrada no site MSDN.
Para fazer o download código fonte do exemplo em Delphi 2005, clique aqui.
Nenhum comentário :
Postar um comentário
OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.
Observação: somente um membro deste blog pode postar um comentário.