Eu tenho um cenário. (Windows Forms, C #, .NET)
- Existe um formulário principal que hospeda algum controle do usuário.
- O controle do usuário realiza algumas operações pesadas de dados, de modo que, se eu chamar diretamente o
UserControl_Load
método, a interface do usuário não responde pela duração da execução do método de carregamento. - Para superar isso, carrego dados em segmentos diferentes (tentando alterar o código existente o mínimo possível)
- Usei um thread de trabalho em segundo plano que carregará os dados e, quando concluído, notificará o aplicativo de que ele fez seu trabalho.
- Agora veio um problema real. Toda a interface do usuário (formulário principal e seus controles de usuário filho) foi criada no encadeamento principal principal. No método LOAD do usercontrol, estou buscando dados com base nos valores de algum controle (como caixa de texto) no userControl.
O pseudocódigo ficaria assim:
CÓDIGO 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
A exceção que deu foi
Operação de encadeamento cruzado inválida: controle acessado a partir de um encadeamento diferente do encadeamento em que foi criado.
Para saber mais sobre isso, pesquisei no Google e surgiu uma sugestão como usar o código a seguir
CÓDIGO 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
MAS MAS MAS ... parece que estou de volta à estaca zero. O Aplicativo novamente não responde. Parece ser devido à execução da linha # 1 se condição. A tarefa de carregamento é novamente executada pelo encadeamento pai e não o terceiro que eu criei.
Não sei se percebi isso certo ou errado. Eu sou novo no segmento.
Como resolvo isso e também qual é o efeito da execução da Linha # 1 se bloco?
A situação é a seguinte : desejo carregar dados em uma variável global com base no valor de um controle. Não quero alterar o valor de um controle do thread filho. Eu nunca vou fazer isso a partir de um thread filho.
Portanto, basta acessar o valor para que os dados correspondentes possam ser buscados no banco de dados.
fonte
Respostas:
De acordo com o comentário de atualização de Prerak K (desde que excluído):
A solução que você deseja deve ter a seguinte aparência:
Faça seu processamento sério no thread separado antes de tentar retornar ao thread do controle. Por exemplo:
fonte
Modelo de encadeamento na interface do usuário
Leia o Modelo de Threading em aplicativos de interface do usuário para entender os conceitos básicos. O link navega para a página que descreve o modelo de encadeamento WPF. No entanto, o Windows Forms utiliza a mesma idéia.
O thread da interface do usuário
Métodos BeginInvoke e Invoke
Invocar
BeginInvoke
Solução de código
Leia as respostas na pergunta Como atualizar a GUI de outro thread em C #? . Para C # 5.0 e .NET 4.5, a solução recomendada está aqui .
fonte
Você deseja usar apenas
Invoke
ouBeginInvoke
para o mínimo trabalho necessário para alterar a interface do usuário. Seu método "pesado" deve ser executado em outro thread (por exemplo, viaBackgroundWorker
), mas depois usandoControl.Invoke
/Control.BeginInvoke
apenas para atualizar a interface do usuário. Dessa forma, seu thread da interface do usuário estará livre para manipular eventos da interface do usuário, etc.Veja meu artigo de threading para obter um exemplo do WinForms - embora o artigo tenha sido escrito antes de
BackgroundWorker
chegar em cena, e receio que não o tenha atualizado a esse respeito.BackgroundWorker
apenas simplifica um pouco o retorno de chamada.fonte
Eu sei que é tarde demais agora. No entanto, ainda hoje, se você estiver com problemas para acessar os controles de thread cruzado? Esta é a resposta mais curta até a data: P
É assim que eu acesso qualquer controle de formulário a partir de um thread.
fonte
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. Eu resolvi aquiEu tive esse problema com o
FileSystemWatcher
e descobri que o código a seguir resolveu o problema:fsw.SynchronizingObject = this
O controle usa o objeto de formulário atual para lidar com os eventos e, portanto, estará no mesmo thread.
fonte
.SynchronizingObject = Me
Acho que o código de verificação e chamada, que precisa ser jogado dentro de todos os métodos relacionados aos formulários, é muito detalhado e desnecessário. Aqui está um método de extensão simples que permite eliminar completamente:
E então você pode simplesmente fazer isso:
Não há mais brincadeiras - simples.
fonte
t
, neste caso, serátextbox1
- é passado como argumentoOs controles no .NET geralmente não são seguros para threads. Isso significa que você não deve acessar um controle de um thread diferente daquele onde ele mora. Para contornar isso, você precisa invocar o controle, que é o que sua segunda amostra está tentando.
No entanto, no seu caso, tudo o que você fez foi passar o método de longa execução de volta ao thread principal. Claro, isso não é realmente o que você quer fazer. Você precisa repensar um pouco isso para que tudo o que você esteja fazendo no thread principal seja definir uma propriedade rápida aqui e ali.
fonte
A solução mais limpa (e adequada) para problemas de segmentação cruzada da interface do usuário é usar SynchronizationContext, consulte Sincronizando chamadas para a interface do usuário em um artigo de aplicativo com vários threads , o que explica muito bem.
fonte
Uma nova aparência usando Async / Await e retornos de chamada. Você só precisa de uma linha de código se mantiver o método de extensão em seu projeto.
Você pode adicionar outras coisas ao método Extension, como agrupá-lo em uma instrução Try / Catch, permitindo que o chamador diga qual tipo retornar após a conclusão, um retorno de chamada de exceção para o chamador:
Adicionando Try Catch, Log de Exceção Automática e CallBack
fonte
Esta não é a maneira recomendada para resolver esse erro, mas você pode suprimi-lo rapidamente, pois ele fará o trabalho. Eu prefiro isso para protótipos ou demos. adicionar
no
Form1()
construtor.fonte
Siga a maneira mais simples (na minha opinião) de modificar objetos de outro segmento:
fonte
Você precisa observar o exemplo do Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Especialmente como ele interage com a camada da interface do usuário. Com base na sua postagem, isso parece responder a seus problemas.
fonte
Encontrei uma necessidade disso ao programar um controlador de aplicativo monotouch para telefone iOS em um projeto de protótipo do visual studio para winforms fora do xamarin stuidio. Preferindo programar no VS sobre o xamarin studio, tanto quanto possível, eu queria que o controlador fosse completamente dissociado da estrutura do telefone. Dessa forma, implementar isso para outras estruturas, como Android e Windows Phone, seria muito mais fácil para usos futuros.
Eu queria uma solução em que a GUI pudesse responder a eventos sem o ônus de lidar com o código de comutação de encadeamento cruzado por trás de cada clique no botão. Basicamente, deixe o controlador de classe lidar com isso para manter o código do cliente simples. Você pode ter muitos eventos na GUI, onde, como se você pudesse lidar com isso em um local da classe, seria mais limpo. Eu não sou um especialista em multithread, deixe-me saber se isso é defeituoso.
O formulário da GUI não sabe que o controlador está executando tarefas assíncronas.
fonte
Aqui está uma maneira alternativa se o objeto com o qual você estiver trabalhando não tiver
Isso é útil se você estiver trabalhando com o formulário principal em uma classe que não seja o formulário principal com um objeto que está no formulário principal, mas não possui InvokeRequired
Funciona da mesma forma que acima, mas é uma abordagem diferente se você não tiver um objeto com requisição invocada, mas tiver acesso ao MainForm
fonte
Na mesma linha das respostas anteriores, mas uma adição muito curta que permite usar todas as propriedades de Controle sem ter a exceção de invocação de thread cruzado.
Método auxiliar
Uso da amostra
fonte
fonte
Por exemplo, para obter o texto de um controle do thread da interface do usuário:
fonte
Mesma pergunta: como atualizar o gui de outro thread em c
Dois caminhos:
Retorne o valor em e.result e use-o para definir o valor da caixa de texto no evento backgroundWorker_RunWorkerCompleted
Declare alguma variável para armazenar esses tipos de valores em uma classe separada (que funcionará como detentor de dados). Crie uma instância estática dessa classe e você poderá acessá-la através de qualquer thread.
Exemplo:
fonte
Simplesmente use isto:
fonte
Ação y; // declarado dentro da classe
label1.Invoke (y = () => label1.Text = "text");
fonte
Maneira simples e reutilizável de solucionar esse problema.
Método de Extensão
Uso da amostra
fonte
Existem duas opções para operações de encadeamento cruzado.
e o segundo é usar
Control.InvokeRequired é útil apenas ao trabalhar com controles herdados da classe Control enquanto SynchronizationContext pode ser usado em qualquer lugar. Algumas informações úteis são as seguintes:
UI de atualização de thread cruzado | .Internet
UI de atualização de thread cruzado usando SynchronizationContext | .Internet
fonte