Uso de Application.DoEvents ()

272

Pode Application.DoEvents()ser usado em c #?

Essa função é uma maneira de permitir que a GUI acompanhe o restante do aplicativo, da mesma maneira que os VB6s DoEventsfazem?

Craig Johnston
fonte
35
DoEventsfaz parte do Windows Forms, não a linguagem C #. Como tal, ele pode ser usado em qualquer linguagem .NET. No entanto, ele não deve ser usado em nenhuma linguagem .NET.
Stephen Cleary
3
É 2017. Use Async / Await. Veja o final da resposta @hansPassant para obter detalhes.
FastAl

Respostas:

468

Hmya, a mística duradoura de DoEvents (). Houve uma enorme quantidade de reação contra isso, mas ninguém realmente explica por que é "ruim". O mesmo tipo de sabedoria que "não modifique uma estrutura". Erm, por que o tempo de execução e a linguagem suportam a mutação de uma estrutura se isso é tão ruim? Mesmo motivo: você atira no próprio pé se não fizer o que é certo. Facilmente. E fazê-lo corretamente requer saber exatamente o que faz, o que no caso de DoEvents () definitivamente não é fácil de entender.

Logo de cara: quase qualquer programa Windows Forms na verdade contém uma chamada para DoEvents (). É inteligentemente disfarçado, porém com um nome diferente: ShowDialog (). É DoEvents () que permite que um diálogo seja modal sem congelar o restante das janelas no aplicativo.

A maioria dos programadores deseja usar o DoEvents para impedir o congelamento da interface do usuário ao escrever seu próprio loop modal. Certamente faz isso; ele envia mensagens do Windows e recebe todos os pedidos de tinta. O problema, porém, é que não é seletivo. Ele não apenas envia mensagens de tinta, mas também fornece todo o resto.

E há um conjunto de notificações que causam problemas. Eles vêm de cerca de um metro na frente do monitor. O usuário pode, por exemplo, fechar a janela principal enquanto o loop que chama DoEvents () está em execução. Isso funciona, a interface do usuário se foi. Mas seu código não parou, ainda está executando o loop. Isso é ruim. Muito muito mal.

Há mais: o usuário pode clicar no mesmo item ou botão de menu que faz com que o mesmo loop seja iniciado. Agora você tem dois loops aninhados executando DoEvents (), o loop anterior é suspenso e o novo loop começa do zero. Isso poderia funcionar, mas, rapaz, as chances são pequenas. Especialmente quando o loop aninhado termina e o suspenso é retomado, tentando concluir um trabalho que já foi concluído. Se isso não bombardear com uma exceção, certamente os dados serão todos embaralhados.

Voltar para ShowDialog (). Ele executa DoEvents (), mas observe que ele faz outra coisa. Ele desativa todas as janelas da aplicação , além do diálogo. Agora que o problema dos 3 pés foi resolvido, o usuário não pode fazer nada para atrapalhar a lógica. Os modos de falha de fechar a janela e iniciar o trabalho novamente estão resolvidos. Ou, de outra forma, não há como o usuário fazer com que seu programa execute o código em uma ordem diferente. Ele será executado de forma previsível, assim como quando você testou seu código. Isso torna os diálogos extremamente irritantes; quem não odeia ter um diálogo ativo e não conseguir copiar e colar algo de outra janela? Mas esse é o preço.

O que é necessário para usar o DoEvents com segurança no seu código. Definir a propriedade Enabled de todos os seus formulários como false é uma maneira rápida e eficiente de evitar problemas. Obviamente, nenhum programador realmente gosta de fazer isso. E não. É por isso que você não deve usar DoEvents (). Você deve usar threads. Mesmo que eles entreguem a você um arsenal completo de maneiras de acertar o pé de maneiras coloridas e inescrutáveis. Mas com a vantagem de que você só atira em seu próprio pé; normalmente não permite que o usuário atire nela.

As próximas versões do C # e do VB.NET fornecerão uma arma diferente com as novas palavras-chave wait e async. Inspirado em parte pelo problema causado por DoEvents e threads, mas em grande parte pelo design da API do WinRT, que exige que você mantenha sua UI atualizada enquanto uma operação assíncrona está ocorrendo. Como ler de um arquivo.

Hans Passant
fonte
1
Esta é apenas a ponta do icebergue, no entanto. Eu tenho usado Application.DoEventssem problemas até adicionar alguns recursos UDP ao meu código, o que resultou no problema descrito aqui . Eu adoraria saber se há uma maneira de contornar isso com DoEvents.
Darda 31/03
Essa é uma boa resposta, a única coisa que sinto falta é uma explicação / discussão sobre como gerar execução e design seguro de segmento ou divisão de tempo em geral - mas isso é facilmente três vezes mais texto novamente. :)
jheriko
5
Se beneficiaria de uma solução mais prática do que geralmente "use threads". Por exemplo, o BackgroundWorkercomponente gerencia threads para você, evitando a maioria dos resultados coloridos do tiro no pé, e não requer versões de linguagem C # de ponta.
Ben Voigt
29

Pode ser, mas é um hack.

Veja DoEvents é mau? .

Direto da página MSDN que thedev referenciados:

A chamada desse método faz com que o encadeamento atual seja suspenso enquanto todas as mensagens da janela em espera são processadas. Se uma mensagem fizer com que um evento seja acionado, outras áreas do código do aplicativo poderão ser executadas. Isso pode fazer com que seu aplicativo exiba comportamentos inesperados difíceis de depurar. Se você executar operações ou cálculos que demoram muito, geralmente é preferível executar essas operações em um novo encadeamento. Para obter mais informações sobre programação assíncrona, consulte Visão geral da programação assíncrona.

Portanto, a Microsoft adverte contra seu uso.

Além disso, considero um hack porque seu comportamento é imprevisível e propenso a efeitos colaterais (isso vem da experiência de tentar usar DoEvents em vez de girar um novo thread ou usar o trabalhador em segundo plano).

Não há machismo aqui - se funcionasse como uma solução robusta, eu estaria por toda parte. No entanto, tentar usar DoEvents no .NET me causou apenas dor.

RQDQ
fonte
1
Vale ressaltar que esse post é de 2004, antes do .NET 2.0 e BackgroundWorkerajudou a simplificar a maneira "correta".
Justin
Acordado. Além disso, a biblioteca de tarefas no .NET 4.0 também é bastante agradável.
RQDQ
1
Por que seria um hack se a Microsoft forneceu uma função para a finalidade exata para a qual é frequentemente necessária?
Craig Johnston
1
@ Craig Johnston - atualizei minha resposta com mais detalhes, por que acredito que o DoEvents se enquadra na categoria hackery.
RQDQ 03/03
Esse artigo de terror sobre codificação se referia a ele como "DoEvents spackle". Brilhante!
precisa saber é o seguinte
24

Sim, há um método estático DoEvents na classe Application no espaço para nome System.Windows.Forms. System.Windows.Forms.Application.DoEvents () pode ser usado para processar as mensagens em espera na fila no thread da interface do usuário ao executar uma tarefa de longa duração no thread da interface do usuário. Isso tem o benefício de fazer com que a interface do usuário pareça mais responsiva e não "bloqueada" enquanto uma tarefa longa está em execução. No entanto, essa nem sempre é a melhor maneira de fazer as coisas. De acordo com a Microsoft, a chamada DoEvents "... faz com que o segmento atual seja suspenso enquanto todas as mensagens da janela em espera são processadas". Se um evento for disparado, existe o potencial de erros inesperados e intermitentes que são difíceis de rastrear. Se você tiver uma tarefa extensa, é muito melhor executá-la em um thread separado. A execução de tarefas longas em um encadeamento separado permite que elas sejam processadas sem interferir com a interface do usuário, continuando a funcionar sem problemas. Vejaaqui para mais detalhes.

Aqui está um exemplo de como usar DoEvents; Observe que a Microsoft também recomenda cautela contra o uso.

Bill W
fonte
13

Pela minha experiência, aconselho muita cautela ao usar DoEvents no .NET. Eu experimentei alguns resultados muito estranhos ao usar DoEvents em um TabControl contendo DataGridViews. Por outro lado, se tudo o que você está lidando é com um pequeno formulário com uma barra de progresso, pode estar tudo bem.

A linha inferior é: se você usará o DoEvents, precisará testá-lo completamente antes de implantar seu aplicativo.

Craig Johnston
fonte
Boa resposta, mas se eu sugerir uma solução para a coisa da barra de progresso melhor , diria: faça seu trabalho em um encadeamento separado, disponibilize seu indicador de progresso em uma variável volátil e intertravada e atualize sua barra de progresso de um temporizador . Dessa forma, nenhum codificador de manutenção é tentado a adicionar código pesado ao seu loop.
mg30rg
1
DoEvents ou equivalente é inevitável se você tiver processos pesados ​​da interface do usuário e não quiser bloquear a interface do usuário. A primeira opção é não executar grandes partes do processamento da interface do usuário, mas isso pode tornar o código menos sustentável. No entanto, aguarde Dispatcher.Yield () faz uma coisa muito semelhante a DoEvents e pode essencialmente permitir que um processo de interface do usuário que bloqueie a tela seja feito para todas as intenções e propósitos assíncronos.
Melbourne Developer
11

Sim.

No entanto, se você precisar usar Application.DoEvents, isso é principalmente uma indicação de um design de aplicativo incorreto. Talvez você queira fazer algum trabalho em um thread separado?

Frederik Gheysels
fonte
3
e se você quiser, para poder girar e aguardar o trabalho para concluir em outro segmento?
jheriko
@heriko Então você realmente deve tentar async-waiting.
mg30rg
5

Eu vi o comentário de jheriko acima e estava inicialmente concordando que não poderia encontrar uma maneira de evitar o uso de DoEvents se você girar o thread principal da interface do usuário esperando por um longo código de código assíncrono em outro thread para concluir. Mas, pela resposta de Matthias, uma simples atualização de um pequeno painel na minha interface do usuário pode substituir os DoEvents (e evitar um efeito colateral desagradável).

Mais detalhes no meu caso ...

Eu estava fazendo o seguinte (conforme sugerido aqui ) para garantir que uma tela inicial do tipo barra de progresso ( como exibir uma sobreposição de "carregamento" ... ) fosse atualizada durante um comando SQL de execução longa:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

O ruim: para mim, ligar para o DoEvents significava que, às vezes, os cliques do mouse eram disparados nos formulários atrás da minha tela inicial, mesmo que eu fosse o TopMost.

A resposta / resposta boa: Substitua a linha DoEvents por uma simples chamada de Atualização em um pequeno painel no centro da minha tela inicial FormSplash.Panel1.Refresh(),. A interface do usuário é atualizada de maneira agradável e a estranheza que os DoEvents já avisaram desapareceu.

TamW
fonte
3
A atualização não atualiza a janela. Se um usuário selecionar outra janela na área de trabalho, clicar novamente em sua janela não terá efeito e o sistema operacional listará seu aplicativo como não responsivo. DoEvents () faz muito mais que uma atualização, pois interage com o sistema operacional através do sistema de mensagens.
ThunderGr 5/12
4

Eu já vi muitas aplicações comerciais, usando o "DoEvents-Hack". Especialmente quando a renderização entra em cena, muitas vezes vejo isso:

while(running)
{
    Render();
    Application.DoEvents();
}

Todos eles sabem sobre o mal desse método. No entanto, eles usam o hack, porque não conhecem outra solução. Aqui estão algumas abordagens retiradas de uma postagem de blog de Tom Miller :

  • Defina seu formulário para que todos os desenhos ocorram no WmPaint e faça sua renderização lá. Antes do final do método OnPaint, verifique isso.Invalidate (); Isso fará com que o método OnPaint seja acionado novamente imediatamente.
  • P / Invoque a API do Win32 e chame PeekMessage / TranslateMessage / DispatchMessage. (Events realmente faz algo semelhante, mas você pode fazer isso sem as alocações extras).
  • Escreva sua própria classe de formulários, que é um pequeno invólucro em torno do CreateWindowEx, e ofereça controle total sobre o loop da mensagem. -Decida que o método DoEvents funciona bem para você e fique com ele.
Matthias
fonte
3

Confira a documentação do MSDN para o Application.DoEventsmétodo

thedev
fonte
3

O DoEvents permite ao usuário clicar ou digitar e acionar outros eventos, e os threads em segundo plano são uma abordagem melhor.

No entanto, ainda existem casos em que você pode encontrar problemas que exijam a liberação de mensagens de eventos. Encontrei um problema em que o controle RichTextBox estava ignorando o método ScrollToCaret () quando o controle tinha mensagens na fila para processar.

O código a seguir bloqueia todas as entradas do usuário durante a execução de DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
cat_in_hat
fonte
1
Você sempre deve bloquear eventos ao chamar eventos. Caso contrário, outros eventos no seu aplicativo serão acionados em resposta e ele poderá começar a fazer duas coisas ao mesmo tempo.
FastAl
2

Application.DoEvents pode criar problemas, se algo diferente do processamento gráfico for colocado na fila de mensagens.

Pode ser útil para atualizar as barras de progresso e notificar o usuário sobre o progresso em algo como construção e carregamento do MainForm, se isso demorar um pouco.

Em um aplicativo recente que criei, usei o DoEvents para atualizar alguns rótulos em uma Tela de Carregamento toda vez que um bloco de código é executado no construtor do meu MainForm. O encadeamento da interface do usuário estava, nesse caso, ocupado com o envio de um email em um servidor SMTP que não suportava chamadas SendAsync (). Provavelmente eu poderia ter criado um thread diferente com os métodos Begin () e End () e chamado Send () a partir deles, mas esse método é propenso a erros e eu preferiria que o Formulário Principal do meu aplicativo não emitisse exceções durante a construção.

Convidado123
fonte