operação de Cross-segmento não é válida: controle acessado de um thread diferente do thread que foi criado

StackOverflow https://stackoverflow.com/questions/142003

Pergunta

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

  1. Há um formulário principal, que abriga cerca de controle do usuário.
  2. O controle de usuário faz alguma operação de dados pesado, de tal forma que se eu chamar diretamente o método UserControl_Load a interface do usuário se tornam inadequadas para a duração para a execução do método de carga.
  3. Para superar esses dados eu carregar no segmento diferente (tentando mudar o código existente tão pouco como eu posso)
  4. Eu usei um segmento de trabalho de fundo que será carregar os dados e quando feito irá notificar o aplicativo que ele fez o seu trabalho.
  5. Agora veio um problema real. Toda a UI (formulário principal e seus usercontrols criança) foi criado no segmento principal primário. No método Load do usercontrol Estou busca de dados com base nos valores de algum controle (como caixa de texto) em 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 dava era

Cross-thread operação não é válida: controle acessado de um thread diferente do thread que foi criado

.

Para saber mais sobre este Fiz algumas googling e uma sugestão surgiu como usando o seguinte código

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 eu estou de volta para um quadrado. O aplicativo novamente tornar sem resposta. Parece ser devido à execução da linha # 1 se a condição. A tarefa de carregamento é novamente feito pelo segmento pai e não o terceiro que eu gerado.

Eu não sei se percebi esta certo ou errado. Eu sou novo para threading.

Como posso resolver isso e também qual é o efeito da execução da Linha # 1 se block?

A situação é a seguinte : Eu quero carregar dados em uma variável global com base no valor de um controle. Eu não quero mudar o valor de um controle do segmento infantil. Eu não vou fazê-lo já a partir de um segmento de criança.

Assim, apenas acessando o valor para que os dados correspondentes pode ser obtido a partir da base de dados.

Foi útil?

Solução

De acordo com o Prerak K atualização comentário (excluída):

Eu acho que não apresentaram a pergunta corretamente.

situação é esta: eu quero carregar dados em uma variável global com base no valor de um controle. Eu não quero mudar o valor de um controle do segmento infantil. Eu não vou fazê-lo já a partir de um segmento de criança.

Assim, apenas acessar o valor para que os dados correspondentes pode ser obtido a partir da base de dados.

A solução que você quer, então deve ser parecido:

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

Faça o seu processamento grave no segmento separado antes você tentar voltar para lista de discussão 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;
        }
    }
}

Outras dicas

Modelo de Threading na UI

Por favor, leia o Threading Model na UI aplicações, a fim de compreender os conceitos básicos. O link navega para página que descreve o modelo de segmentação WPF. No entanto, Windows Forms utiliza a mesma idéia.

O segmento interface do usuário

  • Não é apenas um thread (thread UI), que é permitido o acesso System.Windows.Forms.Control e suas subclasses membros.
  • Tentativa de membro do acesso dos System.Windows.Forms .Control a partir de fios diferente do segmento interface do usuário fará com exceção cross-fio.
  • Uma vez que existe apenas um segmento, todas as operações de interface do usuário são colocados em fila como itens de trabalho para esse segmento:

enter descrição da imagem aqui

enter descrição da imagem aqui

métodos

BeginInvoke e invocar

enter descrição da imagem aqui

Invoke

enter descrição da imagem aqui

BeginInvoke

entrar descriptio imagemn aqui

solução de código

Leia as respostas em questão Como atualizar o GUI de outro segmento em C #? . Para C # 5.0 e .NET 4.5 A solução recomendada é aqui .

Você só quer usar Invoke ou BeginInvoke para a peça mínimo de trabalho necessário para mudar a interface do usuário. Seu método de "pesado" deve ser executado em outro segmento (por exemplo, via BackgroundWorker), mas, em seguida, usando Control.Invoke / Control.BeginInvoke apenas para atualizar a interface do usuário. Dessa forma, o segmento interface do usuário será livre para lidar com eventos de interface etc.

Ver o meu rosqueamento artigo para uma WinForms exemplo - embora o artigo foi escrito antes BackgroundWorker chegaram ao local, e eu tenho medo que eu não tenha atualizado em esse respeito. BackgroundWorker apenas simplifica a chamada de retorno um pouco.

Eu tive esse problema com o FileSystemWatcher e descobriu que o seguinte código resolveu o problema:

fsw.SynchronizingObject = this

O controle, em seguida, usa o objeto atual forma de lidar com os eventos, e será, portanto, no mesmo segmento.

Eu sei que é muito tarde agora. No entanto, ainda hoje, se você está tendo problemas para acessar os controles de rosca cruzada? Esta é a resposta mais curta até a data: P

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

Isto é como eu acessar qualquer controle de formulário a partir de um fio.

Controls em .NET são geralmente não thread-safe. Isso significa que você não deve acessar um controle de um segmento diferente daquele onde vive. Para contornar este problema, você precisa invoke do controle, que é o que sua segunda amostra está tentando.

No entanto, no seu caso tudo que você fez é passar o método de volta longa para o segmento principal. Claro, isso não é realmente o que você quer fazer. Você precisa repensar esta um pouco para que tudo que você está fazendo no segmento principal é a criação de uma propriedade rápido aqui e ali.

I encontrar o código de check-e-invoke que precisa ser cheio em todos os métodos relacionados a formas de ser muito detalhado e desnecessário. Aqui está um método simples extensão que permite acabar com ela 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, em seguida, você pode simplesmente fazer isso:

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

Não mais brincando -. Simples

A solução mais limpa (e adequado) para UI cruzar-threading questões é usar SynchronizationContext, consulte Sincronização de chamadas para a interface do usuário no artigo uma aplicação multi-threaded , ele explica isso muito bem.

Um novo olhar usando Async / Await e retornos de chamada. Você só precisa de uma linha de código, se você manter 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 para o método de extensão como envolvê-lo em uma declaração try / catch, permitindo chamadas para dizer-lhe que tipo de retornar após a conclusão, um retorno de chamada exceção a chamada:

Adicionando Try Catch, Logging Exceção Auto 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
        }
    }

Você precisa olhar para o exemplo backgroundworker:
http://msdn.microsoft.com/en-us/library /system.componentmodel.backgroundworker.aspx Especialmente como ele interage com a camada de interface do usuário. Com base na sua postagem, este parece responder às suas questões.

Siga a maneira mais simples (na minha opinião) para 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;
        }
    }
}

Eu encontrei uma necessidade para este durante a programação de um controlador MonoTouch aplicativo iOS-Phone em um visual studio winforms fora projeto protótipo do stuidio Xamarin. Preferindo programa no VS sobre Xamarin estúdio, tanto quanto possível, eu queria que o controlador para ser completamente dissociada da estrutura celular. Desta forma implementação deste para outros frameworks como Android e Windows Phone seria muito mais fácil para usos futuros.

Eu queria uma solução onde a GUI poderia responder a eventos sem o fardo de lidar com o código de comutação de segmentação cruzada por trás de cada clique de botão. Basicamente deixe a alça controlador de classe que para manter o simples código do cliente. Você poderia, eventualmente, ter muitos eventos na GUI onde, como se você poderia lidar com isso em um só lugar na classe seria mais limpo. Eu não sou um especialista em vários theading, deixe-me saber se isso é falho.

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 GUI desconhece o controlador está em execução 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);
            }
        }
    }
}

Esta não é a maneira recomendada para resolver este erro, mas você pode suprimi-lo rapidamente, ele vai fazer o trabalho. Eu prefiro este para protótipos ou demos. add

CheckForIllegalCrossThreadCalls = false

no construtor Form1().

Aqui está uma forma alternativa se o objeto que você está trabalhando não tem

(InvokeRequired)

Isto é útil se você estiver trabalhando com o formulário principal em uma classe diferente do formulário principal com um objeto que está na forma principal, mas não tem 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;
}

Ele funciona da mesma como acima, mas é uma abordagem diferente se você não tem um objeto com InvokeRequired, mas não têm acesso à MainForm

Ao longo das mesmas linhas como respostas anteriores, mas uma muito curta além disso, que permite a utilização de todas as propriedades de controlo sem ter excepção rosca invocação cruz.

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 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;
}
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

Por exemplo, para obter o texto a partir de um controle do segmento 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

Mesma pergunta: how-to-update- a-gui-de-outra-thread-in-c

Duas maneiras:

  1. O valor de retorno em e.result e usá-lo para definir yout valor caixa de texto no evento backgroundWorker_RunWorkerCompleted

  2. Declare alguma variável para segurar este tipo de valores em uma classe separada (que funcionará como suporte de dados). Criar instância estática desta classe adn você pode acessá-lo sobre qualquer tópico.

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";
    }
}

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

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

Basta usar este:

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

Existem duas opções para operações de rosca cruzada.

Control.InvokeRequired Property 

e segundo é a utilização

SynchronizationContext Post Method

Control.InvokeRequired só é útil quando os controles herdados da classe Control trabalhando enquanto SynchronizationContext pode ser usado em qualquer lugar. Algumas informações úteis é o seguinte ligações

Cruz Tópico UI Update | .Net

Cruz Tópico UI Update usando SynchronizationContext | .Net

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top