O lado sombrio da aplicação. ProcessMessages

Artigo enviado por Marcus Junglas

Ao programar um manipulador de eventos no Delphi (como o OnClick evento de um TButton), chega o momento em que seu aplicativo precisa ficar ocupado por um tempo, por exemplo, o código precisa escrever um arquivo grande ou compactar alguns dados.

Se você fizer isso, notará que seu aplicativo parece estar bloqueado. Seu formulário não pode mais ser movido e os botões não mostram sinal de vida. Parece ter caído.

O motivo é que um aplicativo Delpi é de thread único. O código que você está escrevendo representa apenas um monte de procedimentos que são chamados pelo thread principal do Delphi sempre que um evento ocorre. No restante do tempo, o encadeamento principal lida com mensagens do sistema e outras coisas, como funções de manipulação de formulários e componentes.

Portanto, se você não concluir a manipulação de eventos fazendo um trabalho demorado, impedirá que o aplicativo manipule essas mensagens.

Uma solução comum para esse tipo de problema é chamar "Aplicativo. ProcessMessages ". "Aplicativo" é um objeto global da classe TApplication.

instagram viewer

A aplicação. As mensagens de processo lidam com todas as mensagens em espera, como movimentos da janela, cliques de botão e assim por diante. É comumente usado como uma solução simples para manter seu aplicativo "funcionando".

Infelizmente, o mecanismo por trás de "ProcessMessages" possui características próprias, o que pode causar grande confusão!

O que ProcessMessages?

PprocessMessages manipula todas as mensagens do sistema em espera na fila de mensagens dos aplicativos. O Windows usa mensagens para "conversar" com todos os aplicativos em execução. A interação do usuário é trazida para o formulário por meio de mensagens e "ProcessMessages" lida com elas.

Se o mouse estiver pressionando um TButton, por exemplo, ProgressMessages fará tudo o que deve acontecer nesse evento, como o repintar o botão para um estado "pressionado" e, é claro, uma chamada para o procedimento de manuseio OnClick () se você tiver atribuído um.

Esse é o problema: qualquer chamada para ProcessMessages pode conter uma chamada recursiva para qualquer manipulador de eventos novamente. Aqui está um exemplo:

Use o código a seguir para o manipulador uniforme OnClick de um botão ("trabalho"). A declaração for simula um longo trabalho de processamento com algumas chamadas para ProcessMessages de vez em quando.

Isso é simplificado para melhor legibilidade:

{no MyForm:}
Nível de trabalho: inteiro;
{OnCreate:}
Nível de trabalho: = 0;
procedimento TForm1.WorkBtnClick (Sender: TObject);
var
ciclo: inteiro;
início
inc (nível de trabalho);
para ciclo: = 1 para 5 Faz
início
Memo1.Lines. Adicionar ('- Trabalho' + IntToStr (WorkLevel) + ', Ciclo' + IntToStr (ciclo);
Inscrição. ProcessMessages;
dormir (1000); // ou algum outro trabalho
fim;
Memo1.Lines. Add ('Trabalho' + IntToStr (WorkLevel) + 'finalizado.');
dec (Nível de trabalho);
fim;

SEM "ProcessMessages", as seguintes linhas são gravadas no memorando, se o botão foi pressionado duas vezes em pouco tempo:

 - Trabalho 1, Ciclo 1
- Trabalho 1, Ciclo 2
- Trabalho 1, Ciclo 3
- Trabalho 1, ciclo 4
- Trabalho 1, Ciclo 5
O trabalho 1 terminou.
- Trabalho 1, Ciclo 1
- Trabalho 1, Ciclo 2
- Trabalho 1, Ciclo 3
- Trabalho 1, ciclo 4
- Trabalho 1, Ciclo 5
O trabalho 1 terminou.

Enquanto o procedimento está ocupado, o formulário não mostra nenhuma reação, mas o segundo clique foi colocado na fila de mensagens pelo Windows. Logo após o "OnClick" terminar, ele será chamado novamente.

INCLUINDO "ProcessMessages", a saída pode ser muito diferente:

 - Trabalho 1, Ciclo 1
- Trabalho 1, Ciclo 2
- Trabalho 1, Ciclo 3
- Trabalho 2, Ciclo 1
- Trabalho 2, Ciclo 2
- Trabalho 2, Ciclo 3
- Trabalho 2, Ciclo 4
- Trabalho 2, Ciclo 5
O trabalho 2 terminou.
- Trabalho 1, ciclo 4
- Trabalho 1, Ciclo 5
O trabalho 1 terminou.

Desta vez, o formulário parece estar funcionando novamente e aceita qualquer interação do usuário. Portanto, o botão é pressionado até a metade durante sua primeira função "trabalhador" novamente, que será tratada instantaneamente. Todos os eventos recebidos são tratados como qualquer outra chamada de função.

Em teoria, durante cada chamada para "ProgressMessages", QUALQUER quantidade de cliques e mensagens do usuário pode ocorrer "no local".

Portanto, tenha cuidado com o seu código!

Exemplo diferente (em pseudo-código simples!):

procedimento OnClickFileWrite ();
var myfile: = TFileStream;
início
myfile: = TFileStream.create ('myOutput.txt');
experimentar
enquanto BytesReady> 0 Faz
início
meu arquivo. Gravação (DataBlock);
dec (BytesReady, sizeof (DataBlock));
DataBlock [2]: = # 13; {linha de teste 1}
Inscrição. ProcessMessages;
DataBlock [2]: = # 13; {linha de teste 2}
fim;
finalmente
myfile.free;
fim;
fim;

Essa função grava uma grande quantidade de dados e tenta "desbloquear" o aplicativo usando "ProcessMessages" cada vez que um bloco de dados é gravado.

Se o usuário clicar no botão novamente, o mesmo código será executado enquanto o arquivo ainda estiver sendo gravado. Portanto, o arquivo não pode ser aberto uma segunda vez e o procedimento falha.

Talvez seu aplicativo faça alguma recuperação de erro, como liberar os buffers.

Como possível resultado, o "Datablock" será liberado e o primeiro código "repentinamente" gerará uma "Violação de acesso" quando o acessar. Nesse caso: a linha de teste 1 funcionará, a linha de teste 2 falhará.

A melhor maneira:

Para facilitar, você pode definir todo o formulário "enabled: = false", que bloqueia todas as entradas do usuário, mas NÃO mostra isso ao usuário (todos os botões não estão acinzentados).

Uma maneira melhor seria definir todos os botões como "desativados", mas isso pode ser complexo se você quiser manter um botão "Cancelar", por exemplo. Além disso, você precisa passar por todos os componentes para desativá-los e, quando eles estiverem ativados novamente, precisará verificar se ainda resta algum estado no estado desativado.

Você poderia desabilitar um controle filho de contêiner quando a propriedade Enabled for alterada.

Como o nome da classe "TNotifyEvent" sugere, ele deve ser usado apenas para reações de curto prazo ao evento. Para um código demorado, a melhor maneira é IMHO colocar todo o código "lento" em um próprio Thread.

Em relação aos problemas com "PrecessMessages" e / ou à ativação e desativação de componentes, o uso de um segundo segmento parece não ser muito complicado.

Lembre-se de que mesmo linhas de código simples e rápidas podem travar por segundos, por exemplo a abertura de um arquivo em uma unidade de disco pode ter que esperar até que a rotação da unidade termine. Não parece muito bom se o aplicativo parecer travar porque a unidade está muito lenta.

É isso aí. Na próxima vez que você adicionar "Aplicativo. ProcessMessages ", pense duas vezes;)

instagram story viewer