Compact Framework/Threading - MessageBox é exibido sobre outros controles após a opção ser escolhida
-
08-06-2019 - |
Pergunta
Estou trabalhando em um aplicativo que captura e instala várias atualizações de um servidor externo e preciso de ajuda com threading.O usuário segue este processo:
- Botão de cliques
- O método verifica se há atualizações, a contagem é retornada.
- Se for maior que 0, pergunte ao usuário se deseja instalar usando MessageBox.Show().
- Se sim, ele executa um loop e chama BeginInvoke() no método run() de cada atualização para executá-lo em segundo plano.
- Minha classe de atualização possui alguns eventos que são usados para atualizar uma barra de progresso, etc.
As atualizações da barra de progresso estão corretas, mas o MessageBox não é totalmente apagado da tela porque o loop de atualização começa logo após o usuário clicar em sim (veja a captura de tela abaixo).
- O que devo fazer para que a caixa de mensagens desapareça instantaneamente antes do início do ciclo de atualização?
- Devo usar Threads em vez de BeginInvoke()?
- Devo fazer a verificação inicial da atualização em um thread separado e chamar MessageBox.Show() desse thread?
Código
// Button clicked event handler code...
DialogResult dlgRes = MessageBox.Show(
string.Format("There are {0} updates available.\n\nInstall these now?",
um2.Updates.Count), "Updates Available",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2
);
if (dlgRes == DialogResult.Yes)
{
ProcessAllUpdates(um2);
}
// Processes a bunch of items in a loop
private void ProcessAllUpdates(UpdateManager2 um2)
{
for (int i = 0; i < um2.Updates.Count; i++)
{
Update2 update = um2.Updates[i];
ProcessSingleUpdate(update);
int percentComplete = Utilities.CalculatePercentCompleted(i, um2.Updates.Count);
UpdateOverallProgress(percentComplete);
}
}
// Process a single update with IAsyncResult
private void ProcessSingleUpdate(Update2 update)
{
update.Action.OnStart += Action_OnStart;
update.Action.OnProgress += Action_OnProgress;
update.Action.OnCompletion += Action_OnCompletion;
//synchronous
//update.Action.Run();
// async
IAsyncResult ar = this.BeginInvoke((MethodInvoker)delegate() { update.Action.Run(); });
}
Captura de tela
Solução
Sua IU não está sendo atualizada porque todo o trabalho está acontecendo no thread da interface do usuário.Sua chamada para:
this.BeginInvoke((MethodInvoker)delegate() {update.Action.Run(); })
está dizendo invocar update.Action.Run() no thread que criou "this" (seu formulário), que é o thread da interface do usuário.
Application.DoEvents()
de fato, dará ao thread da interface do usuário a chance de redesenhar a tela, mas ficaria tentado a criar um novo delegado e chamar BeginInvoke sobre isso.
Isso executará a função update.Action.Run() em um thread separado alocado no pool de threads.Você pode então continuar verificando o IAsyncResult até que a atualização seja concluída, consultando o objeto de atualização quanto ao seu progresso após cada verificação (porque você não pode fazer com que o outro thread atualize a barra de progresso/IU) e, em seguida, chamando Application.DoEvents().
Você também deve chamar EndInvoke() depois, caso contrário você pode acabar vazando recursos
Eu também ficaria tentado a colocar um botão cancelar na caixa de diálogo de progresso e adicionar um tempo limite; caso contrário, se a atualização travar (ou demorar muito), seu aplicativo ficará bloqueado para sempre.
Outras dicas
Você já tentou colocar um
Application.DoEvents()
aqui
if (dlgRes == DialogResult.Yes)
{
Application.DoEvents();
ProcessAllUpdates(um2);
}
@John Sably
Você pode se safar não chamar EndInvoke ao lidar com WinForms sem quaisquer consequências negativas.
A única exceção documentada à regra que conheço está no Windows Forms, onde você está oficialmente autorizado a chamar Control.BeginInvoke sem se preocupar em chamar Control.EndInvoke.
No entanto, em todos os outros casos, ao lidar com o padrão Begin/End Async, você deve assumir que ele irá vazar, como afirmou.