Threading e eventos do Windows Forms - ListBox é atualizado imediatamente, mas a barra de progresso sofre um grande atraso
-
08-06-2019 - |
Pergunta
Nossa equipe está criando um novo sistema de fluxo de trabalho de recrutamento para substituir um antigo.Recebi a tarefa de migrar os dados antigos para o novo esquema.Decidi fazer isso criando um pequeno projeto do Windows Forms, pois os esquemas são radicalmente diferentes e os scripts TSQL diretos não são uma solução adequada.
A principal classe selada 'ImportController' que faz o trabalho declara o seguinte evento delegado:
public delegate void ImportProgressEventHandler(object sender, ImportProgressEventArgs e);
public static event ImportProgressEventHandler importProgressEvent;
A janela principal inicia um método estático nessa classe usando um novo thread:
Thread dataProcessingThread = new Thread(new ParameterizedThreadStart(ImportController.ImportData));
dataProcessingThread.Name = "Data Importer: Data Processing Thread";
dataProcessingThread.Start(settings);
os argumentos ImportProgressEvent carregam uma mensagem de string, um valor int máximo para a barra de progresso e um valor int de progresso atual.O formulário do Windows se inscreve no evento:
ImportController.importProgressEvent += new ImportController.ImportProgressEventHandler(ImportController_importProgressEvent);
E responde ao evento desta maneira usando seu próprio delegado:
private delegate void TaskCompletedUIDelegate(string completedTask, int currentProgress, int progressMax);
private void ImportController_importProgressEvent(object sender, ImportProgressEventArgs e)
{
this.Invoke(new TaskCompletedUIDelegate(this.DisplayCompletedTask), e.CompletedTask, e.CurrentProgress, e.ProgressMax);
}
Finalmente a barra de progresso e a caixa de listagem são atualizadas:
private void DisplayCompletedTask(string completedTask, int currentProgress, int progressMax)
{
string[] items = completedTask.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (string item in items)
{
this.lstTasks.Items.Add(item);
}
if (currentProgress >= 0 && progressMax > 0 && currentProgress <= progressMax)
{
this.ImportProgressBar.Maximum = progressMax;
this.ImportProgressBar.Value = currentProgress;
}
}
O problema é que o ListBox parece atualizar muito rapidamente, mas a barra de progresso nunca se move até que o lote esteja quase completo ???o que da ?
Solução 4
@ John
Obrigado por as ligações.
@Will
Não há ganho de threadpooling como eu sei que sempre apenas desova uma vontade rosca. O uso de um fio é puramente para ter uma interface de usuário responsiva enquanto SQL Server está sendo bateu com leituras e gravações. Não é certamente uma rosca curta duração.
Em relação trenó-martelos você está certo. Mas, como se vê meu problema era entre a tela e cadeira depois de tudo. I parecem ter um lote unusal de dados que tem muitos muitos muitos registros chave mais estrangeiros do que os outros lotes e só acontece de ser selecionado no início do processo o que significa a currentProgress não fica ++ 'd para um bom 10 segundos.
@All
Obrigado por toda sua entrada, ele me fez pensar, o que me fez procurar outro lugar no código, o que levou ao meu momento ahaa de humildade onde eu provar mais uma vez o erro é geralmente humana:)
Outras dicas
Talvez você pode tentar o componente BackgroundWorker. Faz enfiar mais fácil. Exemplos aqui:
Talvez fora do escopo, mas, às vezes a sua utilidade para fazer um Application.DoEvents();
para fazer as peças gui reagir a entrada do usuário, como pressionar o botão de cancelar em um diálogo de status bar.
Você por acaso executar o Windows Vista? Eu observei o exatamente o mesmo em algumas aplicações relacionadas com o trabalho. De alguma forma, parece haver um atraso quando a barra de progresso "anima".
Você tem certeza de que o segmento interface do usuário está funcionando livremente durante todo este processo? ou seja, ele não está sentado bloqueado-up em um Junte-se ou alguma outra espera? Isso é o que parece para mim.
A sugestão de usar BackgroundWorker é um bom -. Definitivamente superior para tentar marreta o caminho para sair do problema com uma carga de chamadas de atualização / atualizar
E BackgroundWorker irá utilizar um segmento de pool, que é uma forma mais amigável para se comportar de criar o seu próprio segmento de curta duração.
Não há ganho de threadpooling como Eu sei que só vai gerar uma fio. O uso de um fio é puramente para ter uma interface de usuário responsiva enquanto SQL Servidor está sendo bateu com lê e escreve. Certamente não é um curto fio de duração.
OK, eu aprecio isso, e feliz que você encontrou o seu erro, mas você já olhou para BackgroundWorker? Ele faz muito bonito exatamente o que você está fazendo, mas de forma padronizada (ou seja, sem os seus próprios delegados) e sem a necessidade de criar um novo segmento -. Ambas as quais são (talvez pequeno, mas talvez ainda útil) vantagens