![]() |
Comunicação |
Índice
Introdução
Nesse post iremos abordar 4 Formas de Comunicação entre Threads em Delphi.
A programação multithreaded é uma técnica poderosa que permite a execução simultânea de tarefas, melhorando a eficiência e a responsividade das aplicações. No entanto, a comunicação entre threads pode ser complexa, especialmente quando se trata de garantir a segurança dos dados. Neste artigo, exploraremos os métodos para comunicação segura entre threads em Delphi, apresentando exemplos práticos que ilustram essas técnicas.
Importância da Comunicação entre Threads
Em aplicações que utilizam múltiplas threads, a comunicação é essencial para que as threads possam compartilhar dados e coordenar suas atividades. Um gerenciamento adequado da comunicação ajuda a evitar condições de corrida, deadlocks e corrupção de dados.
Métodos para Comunicação Segura entre Threads
Existem várias abordagens para permitir que threads se comuniquem de forma segura:
- Eventos: Utilizar eventos para notificar outras threads sobre a ocorrência de uma ação.
- Mensagens: Enviar mensagens entre threads utilizando a função
PostMessage
. - Recursos Compartilhados com Sincronização: Utilizar seções críticas (
TCriticalSection
) para proteger o acesso a dados compartilhados. - Queues (Filas): Implementar filas para gerenciar tarefas e dados entre threads de maneira ordenada.
Vamos analisar cada um desses métodos em detalhes, com exemplos.
1. Utilizando Eventos
Os eventos são uma maneira eficiente de notificar uma thread sobre a conclusão de uma tarefa ou a disponibilidade de dados.
Passo 1: Definir a Classe da Thread
uses
System.Classes, SyncObjs;
type
TMyThread1 = class(TThread)
private
FEvent: TEvent;
FData: Integer;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
property Data: Integer read FData;
end;
constructor TMyThread1.Create;
begin
inherited Create(True); // Cria a thread suspensa
FEvent := TEvent.Create(nil, False, False, ''); // Inicializa o evento
end;
destructor TMyThread1.Destroy;
begin
if Assigned(FEvent) then
FEvent.Free; //Finaliza o evento
inherited;
end;
procedure TMyThread1.Execute;
begin
Sleep(1000); // Simula trabalho
FData := 42; // Define um valor
FEvent.SetEvent; // Sinaliza que o trabalho foi concluído
end;
Passo 2: Utilizar a Classe da Thread
// Para usar a thread:
var
MyThread: TMyThread1;
begin
MyThread := TMyThread1.Create;//Cria a Thread
try
MyThread.Start;//Inicia a Thread
MyThread.FEvent.WaitFor(INFINITE); // Aguarda o evento
ShowMessage('Dado recebido: ' + IntToStr(MyThread.Data)); //Exibe a mensagem
finally
MyThread.Free;
end;
end;
Explicação do Código
- Inclusão da Unidade
SyncObjs
:uses System.Classes, SyncObjs;
: Adiciona a unidadeSyncObjs
que contém a declaração da classeTEvent
.
- Definição da Classe
TMyThread1
:TMyThread1 = class(TThread)
: Declara uma nova classe de thread personalizada que herda deTThread
.FEvent: TEvent;
: Declara uma variável de instância do tipoTEvent
para gerenciamento de eventos de sincronização.FData: Integer;
: Declara uma variável de instância para armazenar dados processados pela thread.
- Construtor da Classe
TMyThread1
:constructor Create;
: Inicializa uma nova instância deTMyThread1
.inherited Create(True);
: Chama o construtor da classe baseTThread
com o parâmetroTrue
, que cria a thread em estado suspenso.FEvent := TEvent.Create;
: Cria uma nova instância deTEvent
.
- Destrutor da Classe
TMyThread1
:destructor Destroy; override;
: Sobrescreve o destrutor para liberar os recursos da thread.FEvent.Free;
: Libera a instância deTEvent
.inherited;
: Chama o destrutor da classe base.
- Método
Execute
:procedure Execute; override;
: Sobrescreve o métodoExecute
para definir o código que será executado pela thread.FEvent.WaitFor(INFINITE);
: Aguarda indefinidamente até que o evento seja sinalizado.FData := 42;
: Simula o processamento atribuindo um valor à variávelFData
.
- Utilização da Thread aguardando o evento:
- var MyThread: TMyThread1;: Cria a variavel do tipo TMyThread1.
- MyThread := TMyThread1.Create;: Cria a instância da Thread.
- MyThread.Start; : Inicia a Thread.
- MyThread.FEvent.WaitFor(INFINITE);: Aguarda o evento até que ele seja concluído.
- ShowMessage('Dado recebido: ' + IntToStr(MyThread.Data));: Exibe a mensagem com o dado sincronizado.
- MyThread.Free;: Libera a instância da Thread.
Veja abaixo a ilustração do projeto utilizando eventos:
![]() |
Ilustração de Eventos. |
2. Utilizando Mensagens
Vamos criar uma aplicação Delphi que utiliza threads para realizar uma tarefa em segundo plano e envia mensagens para a interface gráfica utilizando PostMessage
. A função PostMessage
permite enviar mensagens entre threads de forma assíncrona. A tarefa da thread será simplesmente esperar alguns segundos e depois enviar uma mensagem para a interface gráfica indicando que a tarefa foi concluída.
Passo 1: Definir Constante de Mensagem
const
WM_UPDATE = WM_USER + 1;
Passo 2: Definir a Classe da Thread
type
TMyThread2 = class(TThread)
private
FHandle: HWND;
protected
procedure Execute; override;
public
constructor Create(AHandle: HWND);
end;
constructor TMyThread2.Create(AHandle: HWND);
begin
inherited Create(False);
FreeOnTerminate := True; //Finaliza assim que terminar de Executar
FHandle := AHandle;
end;
procedure TMyThread2.Execute;
begin
// Simular uma operação de longa duração
Sleep(3000);
// Enviar mensagem para a interface gráfica
PostMessage(FHandle, WM_UPDATE, 0, 0);
end;
Passo 3: Sobrescrever o Método WndProc
no Formulário
procedure TForm1.WndProc(var Msg: TMessage);
begin
inherited WndProc(Msg);
if Msg.Msg = WM_UPDATE then
ShowMessage('Mensagem recebida de outra thread!');
end;
Passo 4: Iniciar a Thread a partir do Formulário
procedure TForm1.Button1Click(Sender: TObject);
begin
// Iniciar a thread e passar o handle do formulário
TMyThread2.Create(Self.Handle);
end;
Explicação do Código
- Definição da Constante de Mensagem:
WM_UPDATE = WM_USER + 1;
: Define uma constante para a mensagem personalizada que será enviada da thread para o formulário.
- Definição da Classe da Thread:
TMyThread
: Classe que herda deTThread
e sobrescreve o métodoExecute
para realizar uma operação em segundo plano.constructor Create(AHandle: HWND)
: Construtor que aceita umHWND
(handle da janela) como parâmetro e inicia a thread.procedure Execute
: Método onde a operação de longa duração é realizada. Após a operação, a thread envia uma mensagem para a interface gráfica usandoPostMessage
.
- Sobrescrever o Método
WndProc
:WndProc(var Msg: TMessage)
: Método sobrescrito para capturar a mensagem personalizadaWM_UPDATE
. Quando a mensagem é recebida, uma mensagem é mostrada ao usuário.
- Iniciar a Thread a partir do Formulário:
Button1Click
: Método que é chamado quando o botão é clicado. Ele cria uma nova instância deTMyThread
, passando o handle do formulário para que a thread possa enviar mensagens de volta para ele.
Com este exemplo, você pode ver como criar uma thread personalizada em Delphi e utilizar PostMessage
para comunicar a partir da thread com a interface gráfica de forma segura e eficiente.
Veja abaixo a ilustração do projeto utilizando mensagens:
![]() |
Ilustração Mensagens. |
3. Seções Críticas
Quando várias threads precisam acessar um recurso compartilhado, é crucial utilizar uma seção crítica para evitar conflitos.
Nesse exemplo iremos criar 5 Threads que irão compartilhar a variável SharedResource, que será incrementada cada vez que uma nova Thread é criada, e sincronizará os dados na interface gráfica. Dessa forma a variável SharedResource será manipulada de forma segura, através da seção crítica, sempre que uma Thread for utilizá-la.
Passo 1: Criar, inicializar e finalizar uma TCriticalSection
. Criar a variável SharedResource que será o recurso compartilhado.
var
CriticalSection: TCriticalSection;
SharedResource: Integer;
procedure TForm1.FormCreate(Sender: TObject);
begin
CriticalSection := TCriticalSection.Create; //Cria a instância de sessão crítica
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(CriticalSection) then
CriticalSection.Free;//Finaliza a instância de sessão crítica
end;
Passo 2: Implementar a classe da thread que faz uso da seção crítica para acessar o recurso compartilhado.
type
TMyThread3 = class(TThread)
private
FUpdateMethod: TThreadProcedure;
protected
procedure Execute; override;
public
constructor Create(UpdateMethod: TThreadProcedure);
end;
constructor TMyThread3.Create(UpdateMethod: TThreadProcedure);
begin
inherited Create(True); // Create suspended
FUpdateMethod := UpdateMethod;
FreeOnTerminate := True;
end;
procedure TMyThread3.Execute;
begin
CriticalSection.Enter;
try
SharedResource := SharedResource + 1; // Acesso seguro
Sleep(1000); //Espera 1 segundo para dar tempo de exibir o incremento na interface gráfica
Synchronize(FUpdateMethod); // Atualizar a interface de forma segura
finally
CriticalSection.Leave;
end;
end;
Passo 3: Criar instâncias da thread e iniciar sua execução.
procedure TForm1.Button3Click(Sender: TObject);
var
i: Integer;
begin
SharedResource := 0;
for i := 1 to 5 do
begin
TMyThread3.Create(UpdateUI).Start;
end;
end;
Passo 4: Mostrar como utilizar o resultado da thread na interface gráfica ou outro lugar no código principal.
procedure TForm1.UpdateUI;
begin
//Atualiza a interface gráfica com os dados compartilhados
Label1.Caption := 'SharedResource value: ' + SharedResource.ToString;
end;
Explicação do Código
Unit1.pas:
- Declaração das variáveis e tipos principais:
CriticalSection: TCriticalSection
: Seção crítica para controlar o acesso ao recurso compartilhado.SharedResource: Integer
: Recurso compartilhado entre as threads.
- TMyThread3:
- Construtor
Create(UpdateMethod: TThreadProcedure)
: Recebe um método para atualizar a interface quando a thread termina sua execução. - Método
Execute
: Faz o processamento na thread. A seção crítica é usada para garantir acesso exclusivo aoSharedResource
. O métodoSynchronize
é usado para atualizar a interface de forma segura.
- Construtor
- TForm1:
- Botão
Button3
: Ao ser clicado, cria e inicia múltiplas threads. - Método
UpdateUI
: Atualiza a interface gráfica com o valor doSharedResource
.
- Botão
Veja abaixo a ilustração do projeto utilizando Seções críticas para Recursos compartilhados:
![]() |
Ilustração Recursos Compartilhados com Sincronização. |
4. Filas (Queues)
As filas podem ser utilizadas para gerenciar a troca de dados entre threads, garantindo que as tarefas sejam processadas na ordem em que foram adicionadas.
Nesse exemplo vamos criar a classe TTaskQueue que será responsável por criar as tarefas, a Thread TProducerThread que será responsável por colocar as tarefas na fila e a Thread TConsumerThread que será responsável por consumir as tarefas da fila. Para auxiliar nesse projeto iremos utilzar um button e um memo para auxiliar na exibição da execução da fila.
Passo 1: Declarar a variável que será responsável por armazenar a fila
private
TaskQueue: TTaskQueue;
Passo 2: Criar e implementar a Classe TTaskQueue
type
FQueue: TQueue<Integer>;
FLock: TCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure AddTask(Value: Integer);
function GetNextTask: Integer;
end;
constructor TTaskQueue.Create;
begin
FQueue := TQueue<Integer>.Create;
FLock := TCriticalSection.Create;
end;
destructor TTaskQueue.Destroy;
begin
FQueue.Free;
FLock.Free;
inherited;
end;
procedure TTaskQueue.AddTask(Value: Integer);
begin
FLock.Enter;
try
FQueue.Enqueue(Value);
finally
FLock.Leave;
end;
end;
function TTaskQueue.GetNextTask: Integer;
begin
FLock.Enter;
try
if FQueue.Count > 0 then
Result := FQueue.Dequeue
else
Result := -1; // ou algum valor padrão
finally
FLock.Leave;
end;
end;
Passo 3: Criar e implementar a Thread TProducerThread
type
TProducerThread = class(TThread)
private
FTaskQueue: TTaskQueue;
FTaskCount: Integer;
protected
procedure Execute; override;
public
constructor Create(TaskQueue: TTaskQueue; TaskCount: Integer);
end;
constructor TProducerThread.Create(TaskQueue: TTaskQueue; TaskCount: Integer);
begin
inherited Create(True);
FTaskQueue := TaskQueue;
FTaskCount := TaskCount;
FreeOnTerminate := True;
end;
procedure TProducerThread.Execute;
var
I: Integer;
begin
for I := 1 to FTaskCount do
begin
FTaskQueue.AddTask(I);
Sleep(100); // Simula algum trabalho
end;
end;
Passo 4: Criar e implementar a Thread TConsumerThread
type
TConsumerThread = class(TThread)
private
FTaskQueue: TTaskQueue;
FUpdateMethod: TThreadProcedure;
protected
procedure Execute; override;
public
constructor Create(TaskQueue: TTaskQueue; UpdateMethod: TThreadProcedure);
end;
constructor TConsumerThread.Create(TaskQueue: TTaskQueue; UpdateMethod: TThreadProcedure);
begin
inherited Create(True);
FTaskQueue := TaskQueue;
FUpdateMethod := UpdateMethod;
FreeOnTerminate := True;
end;
procedure TConsumerThread.Execute;
var
Task: Integer;
begin
while not Terminated do
begin
Task := FTaskQueue.GetNextTask;
if Task <> -1 then
begin
// Processar a tarefa
Sleep(150); // Simula algum trabalho
// Atualizar a interface
Synchronize(FUpdateMethod);
end
else
begin
// Sem tarefas, dormir um pouco
Sleep(50);
end;
end;
end;
Passo 5: Instanciar a Classe TTaskQueue e as Threads TProducerThread e TConsumerThread e iniciar a execução.
procedure TForm1.Button4Click(Sender: TObject);
var
ProducerThread: TProducerThread;
ConsumerThread: TConsumerThread;
begin
Memo1.Lines.Clear;
TaskQueue := TTaskQueue.Create;
// Criar e iniciar threads produtoras e consumidoras
ProducerThread := TProducerThread.Create(TaskQueue, 10);
ConsumerThread := TConsumerThread.Create(TaskQueue, UpdateLog);
ProducerThread.Start;
ConsumerThread.Start;
end;
Passo 6: Mostrar como exibir a fila sendo consumida.
procedure TForm1.UpdateLog;
begin
Memo1.Lines.Add('Task processed by consumer.');
end;
Explicação do Código
- Classe
TTaskQueue
:- Construtor e Destrutor: Inicializa e limpa os recursos, garantindo que o
TQueue
e aTCriticalSection
sejam criados e destruídos corretamente. - Métodos
AddTask
eGetNextTask
: Manipulam a fila de tarefas de forma segura, usando aTCriticalSection
para sincronização.
- Construtor e Destrutor: Inicializa e limpa os recursos, garantindo que o
- Classe
TProducerThread
:- Construtor: Inicializa a thread com a fila de tarefas e o número de tarefas a serem produzidas.
- Método
Execute
: Adiciona tarefas à fila e simula algum trabalho comSleep
.
- Classe
TConsumerThread
:- Construtor: Inicializa a thread com a fila de tarefas e o método de atualização da interface.
- Método
Execute
: Obtém e processa tarefas da fila, sincronizando a atualização da interface comSynchronize
.
- Formulário
TForm1
:- Método
StartButtonClick
: Inicializa a fila de tarefas e cria threads produtoras e consumidoras. - Método
UpdateLog
: Atualiza a interface gráfica (um memo) para indicar que uma tarefa foi processada.
- Método
Como Funciona
- Thread Produtora: Adiciona tarefas à fila.
- Thread Consumidora: Retira tarefas da fila e processa-as, atualizando a interface do usuário para refletir o progresso.
- Sincronização: A
TCriticalSection
garante que apenas uma thread possa acessar a fila de tarefas de cada vez, prevenindo condições de corrida e garantindo a integridade dos dados.
Veja abaixo as ilustrações do projeto utilizando Filas(Queues):
![]() |
Ilustração de Queue(Fila). |
Código fonte do exemplo
Você pode fazer o download do exemplo do projeto através do repositório do github:
https://github.com/Gisele-de-Melo/ComunicacaoEntreThreads
Se você gostou desse conteúdo, clica no botão abaixo e compartilha com os amigos para me ajudar a espalhar conhecimento.
Conclusão
A comunicação entre threads é um aspecto crítico na programação multithreaded, e é essencial aplicar as técnicas corretas para evitar problemas de sincronização. A escolha do método dependerá do caso de uso específico, e é importante considerar o desempenho e a segurança dos dados.
Neste artigo, exploramos algumas das abordagens mais comuns em Delphi para garantir uma comunicação segura e eficaz entre threads. Ao implementar essas técnicas, você estará melhor preparado para criar aplicações robustas e responsivas.
Essas práticas não apenas melhoram a estrutura do seu código, mas também aumentam a manutenibilidade e a escalabilidade das suas aplicações. Assim, a comunicação entre threads torna-se uma habilidade valiosa para qualquer desenvolvedor Delphi.
Nenhum comentário:
Postar um comentário