Operação de encadeamento inválida: controle acessado a partir de um encadeamento diferente do encadeamento em que foi criado


584

Eu tenho um cenário. (Windows Forms, C #, .NET)

  1. Existe um formulário principal que hospeda algum controle do usuário.
  2. O controle do usuário realiza algumas operações pesadas de dados, de modo que, se eu chamar diretamente o UserControl_Loadmétodo, a interface do usuário não responde pela duração da execução do método de carregamento.
  3. Para superar isso, carrego dados em segmentos diferentes (tentando alterar o código existente o mínimo possível)
  4. 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.
  5. 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.


Para minha instância específica desse erro, achei que a solução alternativa seria usar um BackgroundWorker no formulário para manipular as partes do código com muitos dados. (ou seja, coloque todo o código do problema no método backgroundWorker1_DoWork () e chame-o por backgroundWorker1.RunWorkerAsync ()) ... Essas duas fontes me indicaram a direção correta: stackoverflow.com/questions/4806742/… youtube.com/ Veja? v = MLrrbG6V1zM
Giollia

Respostas:


433

De acordo com o comentário de atualização de Prerak K (desde que excluído):

Acho que não apresentei a pergunta corretamente.

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.

A solução que você deseja deve ter a seguinte aparência:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Faça seu processamento sério no thread separado antes de tentar retornar ao thread do controle. Por exemplo:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

1
Já faz um tempo desde que eu fiz a programação C #, mas com base no artigo do MSDN e no meu conhecimento superficial, parece que sim.
Jeff Hubbard

1
A diferença é que BeginInvoke () é assíncrono enquanto Invoke () é executado de forma síncrona. stackoverflow.com/questions/229554/…
frzsombor 13/10

178

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

  • Há apenas um thread (thread da interface do usuário) que tem permissão para acessar System.Windows.Forms.Control e seus membros de subclasses.
  • A tentativa de acessar o membro de System.Windows.Forms.Control de thread diferente do thread da interface do usuário causará uma exceção de thread cruzado.
  • Como existe apenas um encadeamento, todas as operações da interface do usuário são enfileiradas como itens de trabalho nesse encadeamento:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Métodos BeginInvoke e Invoke

  • A sobrecarga de computação do método que está sendo chamado deve ser pequena, bem como a sobrecarga de computação dos métodos do manipulador de eventos porque o encadeamento da interface do usuário é usado lá - o mesmo que é responsável por manipular a entrada do usuário. Independentemente se esse for System.Windows.Forms.Control.Invoke ou System.Windows.Forms.Control.BeginInvoke .
  • Para executar operações caras de computação, use sempre threads separados. Desde que o .NET 2.0 BackgroundWorker se dedica a executar operações caras de computação no Windows Forms. No entanto, em novas soluções, você deve usar o padrão de espera assíncrona, conforme descrito aqui .
  • Use System.Windows.Forms.Control.Invoke ou System.Windows.Forms.Control.BeginInvoke métodos só para atualizar a interface do usuário. Se você usá-los para cálculos pesados, seu aplicativo bloqueará:

insira a descrição da imagem aqui

Invocar

insira a descrição da imagem aqui

BeginInvoke

insira a descrição da imagem aqui

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 .



72

Você deseja usar apenas Invokeou BeginInvokepara 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, via BackgroundWorker), mas depois usando Control.Invoke/ Control.BeginInvokeapenas 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 BackgroundWorkerchegar em cena, e receio que não o tenha atualizado a esse respeito. BackgroundWorkerapenas simplifica um pouco o retorno de chamada.


aqui nesta minha condição. nem estou mudando a interface do usuário. Estou apenas acessando seus valores atuais a partir do thread filho. qualquer hw sugestão para implementar
Prerak K

1
Você ainda precisa direcionar para o thread da interface do usuário, mesmo apenas para acessar propriedades. Se o seu método não puder continuar até que o valor seja acessado, você poderá usar um delegado que retorne o valor. Mas sim, acesse o thread da interface do usuário.
Jon Skeet

Oi Jon, acredito que você está me dirigindo na direção certa. Sim, eu preciso do valor sem ele não posso continuar. Por favor, você pode eloborar sobre 'Usando um delegado que retorna um valor'. Obrigado
Prerak K

1
Use um representante como Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Isso supondo que você está usando C # 3.0 -. Você poderia usar um método anônimo de outra forma)
Jon Skeet

45

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

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

É assim que eu acesso qualquer controle de formulário a partir de um thread.


1
Isso me dá Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Eu resolvi aqui
rupweb

42

Eu tive esse problema com o FileSystemWatchere 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.


2
Isso salvou meu bacon. No VB.NET eu usei.SynchronizingObject = Me
codingcoding

20

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:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

E então você pode simplesmente fazer isso:

textbox1.Invoke(t => t.Text = "A");

Não há mais brincadeiras - simples.


o que é 't' aqui
Rawat

@Rawat t, neste caso, será textbox1- é passado como argumento
Rob

17

Os 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.



10

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.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

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

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

10

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

CheckForIllegalCrossThreadCalls = false

no Form1()construtor.


9

Siga a maneira mais simples (na minha opinião) de modificar objetos de outro segmento:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

Simples! obrigado .
Ali Esmaeili 19/04


7

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.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

O formulário da GUI não sabe que o controlador está executando tarefas assíncronas.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

6

Aqui está uma maneira alternativa se o objeto com o qual você estiver trabalhando não tiver

(InvokeRequired)

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

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

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


5

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

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Uso da amostra

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}


5

Por exemplo, para obter o texto de um controle do thread da interface do usuário:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

3

Mesma pergunta: como atualizar o gui de outro thread em c

Dois caminhos:

  1. Retorne o valor em e.result e use-o para definir o valor da caixa de texto no evento backgroundWorker_RunWorkerCompleted

  2. 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:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

2

Simplesmente use isto:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

0

Ação y; // declarado dentro da classe

label1.Invoke (y = () => label1.Text = "text");


0

Maneira simples e reutilizável de solucionar esse problema.

Método de Extensão

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Uso da amostra

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

-3

Existem duas opções para operações de encadeamento cruzado.

Control.InvokeRequired Property 

e o segundo é usar

SynchronizationContext Post Method

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.