Qual é a maneira mais simples de atualizar um Label
de outro Thread
?
Estou
Form
em execuçãothread1
, e a partir disso estou iniciando outro thread (thread2
).Enquanto
thread2
está a processar alguns arquivos eu gostaria de atualizar umLabel
noForm
com o estado actual dathread2
obra de.
Como eu pude fazer isso?
c#
.net
multithreading
winforms
user-interface
CruelIO
fonte
fonte
Respostas:
Para o .NET 2.0, aqui está um bom código que escrevi que faz exatamente o que você deseja e funciona para qualquer propriedade em
Control
:Chame assim:
Se você estiver usando o .NET 3.0 ou superior, poderá reescrever o método acima como um método de extensão da
Control
classe, o que simplificaria a chamada para:ATUALIZAÇÃO 10/05/2010:
Para o .NET 3.0, você deve usar este código:
que usa expressões LINQ e lambda para permitir uma sintaxe muito mais limpa, mais simples e segura:
Além de o nome da propriedade agora ser verificado no momento da compilação, o tipo da propriedade também é, portanto, é impossível (por exemplo) atribuir um valor de string a uma propriedade booleana e, portanto, causar uma exceção de tempo de execução.
Infelizmente, isso não impede ninguém de fazer coisas estúpidas, como passar
Control
a propriedade e o valor de outra pessoa , de modo que o seguinte será felizmente compilado:Portanto, adicionei as verificações de tempo de execução para garantir que a propriedade transferida realmente pertença à
Control
qual o método está sendo chamado. Não é perfeito, mas ainda é muito melhor que a versão .NET 2.0.Se alguém tiver mais sugestões sobre como melhorar esse código para segurança em tempo de compilação, comente!
fonte
SetControlPropertyThreadSafe(myLabel, "Text", status)
ser chamado de outro módulo ou classe ou formaA maneira mais simples é um método anônimo passado para
Label.Invoke
:Observe que
Invoke
bloqueia a execução até que ela seja concluída - este é um código síncrono. A pergunta não pergunta sobre código assíncrono, mas há muito conteúdo no Stack Overflow sobre a gravação de código assíncrono quando você deseja aprender sobre ele.fonte
Manuseando trabalhos longos
Desde o .NET 4.5 e C # 5.0, você deve usar o padrão assíncrono baseado em tarefas (TAP) junto com o async - aguarde palavras-chave em todas as áreas (incluindo a GUI):
em vez do modelo de programação assíncrona (APM) e do padrão assíncrono baseado em eventos (EAP) (o último inclui a classe BackgroundWorker ).
Em seguida, a solução recomendada para o novo desenvolvimento é:
Implementação assíncrona de um manipulador de eventos (Sim, isso é tudo):
Implementação do segundo thread que notifica o thread da interface do usuário:
Observe o seguinte:
Para exemplos mais detalhados, consulte: O futuro do C #: coisas boas chegam para aqueles que 'esperam' por Joseph Albahari .
Veja também sobre o conceito de UI Threading Model .
Manipulando exceções
O snippet abaixo é um exemplo de como lidar com exceções e com a
Enabled
propriedade do botão de alternância para impedir vários cliques durante a execução em segundo plano.fonte
SecondThreadConcern.LongWork()
lança uma exceção, ela pode ser capturada pelo thread da interface do usuário? Este é um excelente post, aliás.Task.Delay(500).Wait()
? Qual é o sentido de criar uma tarefa para bloquear apenas o segmento atual? Você nunca deve bloquear um thread do conjunto de threads!Variação da solução mais simples de Marc Gravell para o .NET 4:
Ou use o delegado de ação:
Veja aqui uma comparação dos dois: MethodInvoker vs Action for Control.BeginInvoke
fonte
this.refresh()
de força invalidar e repintar o GUI .. se é útil ..Dispare e esqueça o método de extensão para o .NET 3.5+
Isso pode ser chamado usando a seguinte linha de código:
fonte
@this
é simplesmente o nome da variável; nesse caso, a referência ao controle atual que chama o ramal. Você pode renomeá-lo para a fonte ou o que quer que seja o seu barco. Eu uso@this
, porque está se referindo a 'este controle' que está chamando a extensão e é consistente (pelo menos na minha cabeça) com o uso da palavra-chave 'this' no código normal (sem extensão).OnUIThread
vez deUIThread
.RunOnUiThread
. Mas isso é apenas gosto pessoal.Esta é a maneira clássica de fazer isso:
Seu segmento de trabalho tem um evento. Seu encadeamento da interface do usuário inicia outro encadeamento para executar o trabalho e conecta esse evento de trabalho para que você possa exibir o estado do encadeamento de trabalho.
Na interface do usuário, você precisará cruzar os threads para alterar o controle real ... como um rótulo ou uma barra de progresso.
fonte
A solução simples é usar
Control.Invoke
.fonte
O código de encadeamento geralmente é com erros e sempre difícil de testar. Você não precisa escrever um código de encadeamento para atualizar a interface do usuário de uma tarefa em segundo plano. Basta usar a classe BackgroundWorker para executar a tarefa e seu método ReportProgress para atualizar a interface do usuário. Geralmente, você apenas reporta uma porcentagem concluída, mas há outra sobrecarga que inclui um objeto de estado. Aqui está um exemplo que apenas relata um objeto string:
Tudo bem se você sempre deseja atualizar o mesmo campo. Se você tiver atualizações mais complicadas, poderá definir uma classe para representar o estado da interface do usuário e passá-lo para o método ReportProgress.
Uma coisa final, não se esqueça de definir o
WorkerReportsProgress
sinalizador, ou oReportProgress
método será completamente ignorado.fonte
backgroundWorker1_RunWorkerCompleted
.A grande maioria das respostas usa
Control.Invoke
uma condição de corrida esperando para acontecer . Por exemplo, considere a resposta aceita:Se o usuário fechar o formulário antes de
this.Invoke
ser chamado (lembre-se,this
é oForm
objeto),ObjectDisposedException
provavelmente será acionado.A solução é usar
SynchronizationContext
, especificamenteSynchronizationContext.Current
como sugere hamilton.danielb (outras respostas dependem deSynchronizationContext
implementações específicas que são completamente desnecessárias). Eu modificaria levemente o código dele para usar emSynchronizationContext.Post
vez deSynchronizationContext.Send
fazê-lo (como normalmente não há necessidade de o thread de trabalho aguardar):Observe que, no .NET 4.0 e superior, você realmente deve usar tarefas para operações assíncronas. Veja a resposta da n-san para a abordagem equivalente baseada em tarefas (usando
TaskScheduler.FromCurrentSynchronizationContext
).Por fim, no .NET 4.5 e superior, você também pode usar
Progress<T>
(que basicamente é capturadoSynchronizationContext.Current
após a criação), como demonstrado por Ryszard Degan, nos casos em que a operação de longa duração precisa executar o código da interface do usuário enquanto ainda está trabalhando.fonte
Você precisará garantir que a atualização ocorra no thread correto; o thread da interface do usuário.
Para fazer isso, você precisará chamar o manipulador de eventos em vez de chamá-lo diretamente.
Você pode fazer isso aumentando seu evento assim:
(O código é digitado aqui da minha cabeça, por isso não verifiquei a sintaxe correta, etc., mas deve ajudá-lo.)
Observe que o código acima não funcionará em projetos WPF, pois os controles WPF não implementam a
ISynchronizeInvoke
interface.A fim de se certificar de que o código acima funciona com Windows Forms e WPF, e todas as outras plataformas, você pode ter um olhar para os
AsyncOperation
,AsyncOperationManager
eSynchronizationContext
classes.Para gerar eventos facilmente dessa maneira, criei um método de extensão, que me permite simplificar o aumento de um evento apenas chamando:
Obviamente, você também pode fazer uso da classe BackGroundWorker, que abstrairá esse assunto para você.
fonte
Você precisará chamar o método no thread da GUI. Você pode fazer isso chamando Control.Invoke.
Por exemplo:
fonte
Por causa da trivialidade do cenário, eu realmente teria a pesquisa de threads da interface do usuário para o status. Eu acho que você vai achar que pode ser bem elegante.
A abordagem evita a operação de empacotamento necessária ao usar os métodos
ISynchronizeInvoke.Invoke
eISynchronizeInvoke.BeginInvoke
. Não há nada errado em usar a técnica de empacotamento, mas há algumas ressalvas que você precisa estar ciente.BeginInvoke
muita frequência ou isso pode sobrecarregar a bomba de mensagens.Invoke
no segmento de trabalho é uma chamada de bloqueio. Ele interromperá temporariamente o trabalho que está sendo feito nesse segmento.A estratégia que proponho nesta resposta inverte os papéis de comunicação dos threads. Em vez de o thread do trabalhador enviar os dados, o thread da interface do usuário faz pesquisas. Esse é um padrão comum usado em muitos cenários. Como tudo que você deseja fazer é exibir informações de progresso do thread de trabalho, acho que você descobrirá que essa solução é uma ótima alternativa à solução de empacotamento. Tem as seguintes vantagens.
Control.Invoke
ouControl.BeginInvoke
que os acopla firmemente.fonte
Elapsed
evento, você usar um método de membro para que você possa remover o temporizador quando o formulário é descartado ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
e atribuí-lo viam_Timer.Elapsed += handler;
, mais tarde, no contexto de descarte,m_Timer.Elapsed -= handler;
estou certo? E para o descarte / fechamento seguindo os conselhos discutidos aqui .Nenhum dos itens de Invoke nas respostas anteriores é necessário.
Você precisa olhar para WindowsFormsSynchronizationContext:
fonte
Essa é semelhante à solução acima, usando o .NET Framework 3.0, mas resolveu o problema do suporte à segurança em tempo de compilação .
Usar:
O compilador falhará se o usuário transmitir o tipo de dados errado.
fonte
Salvete! Depois de pesquisar essa pergunta, achei as respostas de FrankG e Oregon Ghost as mais fáceis e úteis para mim. Agora, eu codifico no Visual Basic e executei esse trecho através de um conversor; então não tenho certeza de como isso acontece.
Eu tenho um formulário de diálogo chamado
form_Diagnostics,
que possui uma caixa de richtext, chamadaupdateDiagWindow,
que eu estou usando como uma espécie de exibição de log. Eu precisava ser capaz de atualizar o texto de todos os tópicos. As linhas extras permitem que a janela role automaticamente para as linhas mais recentes.E assim, agora posso atualizar a exibição com uma linha, de qualquer lugar do programa inteiro da maneira que você acha que funcionaria sem nenhum encadeamento:
Código principal (coloque isso dentro do código de classe do seu formulário):
fonte
Para muitos propósitos, é tão simples quanto isto:
"serviceGUI ()" é um método no nível da GUI no formulário (this) que pode alterar quantos controles você desejar. Chame "updateGUI ()" do outro segmento. Os parâmetros podem ser adicionados para transmitir valores ou (provavelmente mais rápido) usar variáveis de escopo de classe com bloqueios, conforme necessário, se houver a possibilidade de um conflito entre os segmentos acessando-os, o que poderia causar instabilidade. Use BeginInvoke em vez de Invoke se o encadeamento que não seja da GUI for crítico em termos de tempo (lembrando o aviso de Brian Gideon).
fonte
Isso na variação do C # 3.0 da solução de Ian Kemp:
Você chama assim:
Caso contrário, o original é uma solução muito agradável.
fonte
Observe que
BeginInvoke()
é preferível aoInvoke()
contrário, porque é menos provável que cause conflitos (no entanto, este não é um problema aqui ao atribuir texto a um rótulo):Ao usar,
Invoke()
você está aguardando o método retornar. Agora, pode ser que você faça algo no código chamado que precisará aguardar o encadeamento, o que pode não ser imediatamente óbvio se estiver oculto em algumas funções que você está chamando, o que pode acontecer indiretamente por meio de manipuladores de eventos. Então você estaria esperando o encadeamento, o encadeamento estaria esperando por você e você está em um impasse.Isso realmente causou a interrupção de alguns dos nossos softwares lançados. Foi fácil de corrigir, substituindo
Invoke()
porBeginInvoke()
. A menos que você precise de operação síncrona, que pode ser o caso se você precisar de um valor de retorno, useBeginInvoke()
.fonte
Quando encontrei o mesmo problema, procurei ajuda do Google, mas, em vez de me dar uma solução simples, ela me confundiu mais, dando exemplos de
MethodInvoker
e blá blá blá. Então eu decidi resolver isso sozinho. Aqui está a minha solução:Faça um delegado como este:
Você pode chamar essa função em um novo thread como este
Não se confunda
Thread(() => .....)
. Uso uma função anônima ou expressão lambda quando trabalho em um thread. Para reduzir as linhas de código, você também pode usar oThreadStart(..)
método que não devo explicar aqui.fonte
Basta usar algo como isto:
fonte
e.ProgressPercentage
, você já não está no thread da interface do usuário do método que está chamando isso?Você pode usar o delegado já existente
Action
:fonte
Minha versão é inserir uma linha de "mantra" recursivo:
Para sem argumentos:
Para uma função que possui argumentos:
É isso .
Alguma argumentação : geralmente é ruim para a legibilidade do código colocar {} após um
if ()
instrução em uma linha. Mas, neste caso, é o mesmo "mantra" rotineiro. Não quebra a legibilidade do código se esse método for consistente com o projeto. E evita que seu código seja jogado fora (uma linha de código em vez de cinco).Como você vê,
if(InvokeRequired) {something long}
você apenas sabe "esta função é segura para chamar de outro thread".fonte
Tente atualizar o rótulo usando este
fonte
Crie uma variável de classe:
Defina-o no construtor que cria sua interface do usuário:
Quando você deseja atualizar o rótulo:
fonte
Você deve usar invocar e delegar
fonte
A maioria das outras respostas é um pouco complexa para mim nesta questão (eu sou novo em C #), então estou escrevendo o meu:
Eu tenho um aplicativo WPF e defini um trabalhador como abaixo:
Questão:
Solução:
Ainda estou para descobrir o que a linha acima significa, mas funciona.
Para WinForms :
Solução:
fonte
A maneira mais fácil que eu penso:
fonte
Por exemplo, acesse um controle que não seja o thread atual:
Existe o
lblThreshold
Label eSpeed_Threshold
é uma variável global.fonte
Quando você está no encadeamento da interface do usuário, pode solicitar seu agendador de tarefas de contexto de sincronização. Daria a você um TaskScheduler que agenda tudo no thread da interface do usuário.
Em seguida, você pode encadear suas tarefas para que, quando o resultado estiver pronto, outra tarefa (agendada no encadeamento da interface do usuário) as escolha e as atribua a um rótulo.
Isso funciona para tarefas (não threads) que são a maneira preferida de escrever código simultâneo agora .
fonte
Task.Start
normalmente não é uma boa prática blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxAcabei de ler as respostas e isso parece ser um tópico muito quente. Atualmente, estou usando o .NET 3.5 SP1 e o Windows Forms.
A fórmula conhecida amplamente descrita nas respostas anteriores que utiliza o InvokeRequired propriedade cobre a maioria dos casos, mas não o pool inteiro.
E se o Handle ainda não tiver sido criado?
A propriedade InvokeRequired , conforme descrito aqui (referência de Propriedade Control.InvokeRequired para MSDN) retorna true se a chamada foi feita a partir de um thread que não seja o thread da GUI, false se a chamada foi feita a partir do thread da GUI ou se o Handle foi ainda não criado.
Você pode encontrar uma exceção se desejar que um formulário modal seja mostrado e atualizado por outro encadeamento. Como você deseja que esse formulário seja mostrado modalmente, você pode fazer o seguinte:
E o delegado pode atualizar um Label na GUI:
Isso pode causar um InvalidOperationException se as operações antes da atualização do rótulo de "levar menos tempo" (lê-lo e interpretá-lo como uma simplificação) do que o tempo que leva para o segmento GUI para criar o formulário 's punho . Isso acontece dentro do ShowDialog () método .
Você também deve verificar o identificador assim:
Você pode manipular a operação para executar se o identificador ainda não tiver sido criado: Você pode simplesmente ignorar a atualização da GUI (como mostrado no código acima) ou pode esperar (mais arriscado). Isso deve responder à pergunta.
Material opcional: Pessoalmente, vim codificando o seguinte:
Alimento meus formulários que são atualizados por outro thread com uma instância deste ThreadSafeGuiCommand e defino métodos que atualizam a GUI (no meu formulário) da seguinte maneira:
Dessa maneira, tenho certeza de que minha GUI será atualizada, independentemente do encadeamento que fará a chamada, opcionalmente, aguardando um período de tempo bem definido (o tempo limite).
fonte