Invocar (delegar)

93

Alguém pode explicar esta declaração escrita neste link

Invoke(Delegate):

Executa o delegado especificado no thread que possui o identificador de janela subjacente do controle.

Alguém pode explicar o que isso significa (especialmente o ousado)? Não sou capaz de entender claramente

user1903439
fonte
4
A resposta a esta pergunta está ligada à propriedade Control.InvokeRequired - consulte msdn.microsoft.com/en-us/library/…
travessão

Respostas:

130

A resposta a esta pergunta está em como funcionam os controles C #

Os controles no Windows Forms são vinculados a um thread específico e não são seguros para thread. Portanto, se você estiver chamando o método de um controle de um thread diferente, você deve usar um dos métodos invoke do controle para empacotar a chamada para o thread adequado. Esta propriedade pode ser usada para determinar se você deve chamar um método invoke, o que pode ser útil se você não souber qual thread possui um controle.

De Control.InvokeRequired

Efetivamente, o que Invoke faz é garantir que o código que você está chamando ocorra no encadeamento em que o controle "vive", evitando efetivamente exceções de encadeamento cruzado.

De uma perspectiva histórica, em .Net 1.1, isso era realmente permitido. O que significava é que você poderia tentar e executar o código no thread "GUI" de qualquer thread em segundo plano e isso funcionaria na maioria das vezes. Às vezes, isso apenas faria com que seu aplicativo fechasse porque você estava interrompendo efetivamente o thread da GUI enquanto ele fazia outra coisa. Esta é a exceção Cross Threaded - imagine tentar atualizar um TextBox enquanto a GUI está pintando outra coisa.

  • Qual ação tem prioridade?
  • É mesmo possível que ambos aconteçam ao mesmo tempo?
  • O que acontece com todos os outros comandos de que a GUI precisa para ser executada?

Na verdade, você está interrompendo uma fila, o que pode ter muitas consequências imprevistas. Invoke é efetivamente a maneira "educada" de colocar o que você deseja fazer na fila, e essa regra foi aplicada a partir do .Net 2.0 por meio de uma InvalidOperationException lançada .

Para entender o que realmente está acontecendo nos bastidores e o que se entende por "Thread da GUI", é útil entender o que é um Message Pump ou Message Loop.

Na verdade, isso já foi respondido na pergunta " O que é uma bomba de mensagem " e é uma leitura recomendada para entender o mecanismo real que você está conectando ao interagir com os controles.

Outras leituras que você pode achar úteis incluem:

O que há com Begin Invoke

Uma das regras principais da programação de GUI do Windows é que apenas o thread que criou um controle pode acessar e / ou modificar seu conteúdo (exceto para algumas exceções documentadas). Tente fazer isso a partir de qualquer outro thread e você obterá um comportamento imprevisível que varia de impasse a exceções a uma interface do usuário parcialmente atualizada. A maneira certa de atualizar um controle de outro encadeamento é postar uma mensagem apropriada na fila de mensagens do aplicativo. Quando a bomba de mensagem começar a executar aquela mensagem, o controle será atualizado, no mesmo thread que o criou (lembre-se, a bomba de mensagem roda no thread principal).

e, para uma visão geral mais pesada do código com uma amostra representativa:

Operações Cruzadas Inválidas

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

Depois de apreciar InvokeRequired, você pode considerar o uso de um método de extensão para encerrar essas chamadas. Isso é habilmente abordado na questão Stack Overflow, Limpando o código cheio de invocação necessária .

Há também um outro relato do que aconteceu historicamente que pode ser de interesse.

traço
fonte
67

Um controle ou objeto de janela no Windows Forms é apenas um invólucro em torno de uma janela Win32 identificada por um identificador (às vezes chamado de HWND). A maioria das coisas que você faz com o controle eventualmente resultará em uma chamada de API do Win32 que usa esse identificador. O identificador pertence ao segmento que o criou (normalmente o segmento principal) e não deve ser manipulado por outro segmento. Se, por algum motivo, você precisar fazer algo com o controle de outro encadeamento, pode usar Invokepara pedir ao encadeamento principal para fazer isso em seu nome.

Por exemplo, se você deseja alterar o texto de um rótulo de um thread de trabalho, pode fazer algo assim:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));
Thomas Levesque
fonte
Você pode explicar por que alguém faria algo assim? this.Invoke(() => this.Enabled = true);O que quer que thisse refira está certamente no tópico atual, certo?
Kyle Delaney
1
@KyleDelaney, um objeto não está "em" um encadeamento e o encadeamento atual não é necessariamente o encadeamento que criou o objeto.
Thomas Levesque
24

Se você deseja modificar um controle, isso deve ser feito no thread em que o controle foi criado. Este Invokemétodo permite executar métodos no thread associado (o thread que possui o identificador de janela subjacente do controle).

No exemplo abaixo, thread1 lança uma exceção porque SetText1 está tentando modificar textBox1.Text de outro thread. Mas no thread2, a ação em SetText2 é executada no thread em que o TextBox foi criado

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}
Mehmet Ataş
fonte
Eu realmente gosto dessa abordagem, ela esconde a natureza dos delegados, mas é um bom atalho de qualquer maneira.
shytikov
6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });
BestDeveloper
fonte
O uso de System.Action que as pessoas sugerem em outras respostas funciona apenas no framework 3.5+, para versões mais antigas, isso funciona perfeitamente
Suicide Platypus
2

Em termos práticos, isso significa que o delegado tem garantia de ser invocado no encadeamento principal. Isso é importante porque, no caso de controles do Windows, se você não atualizar suas propriedades no thread principal, não verá a alteração ou o controle gerará uma exceção.

O padrão é:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}
satnhak
fonte
2

this.Invoke(delegate)certifique-se de que você está chamando o delegado do argumento para this.Invoke()no thread principal / thread criado.

Posso dizer que uma regra Thumb não acessa os controles do formulário, exceto a partir do thread principal.

Pode ser que as seguintes linhas façam sentido para usar Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Há situações em que você cria um thread Threadpool (isto é, thread de trabalho), ele será executado no thread principal. Ele não criará um novo thread porque o thread principal está disponível para processar instruções adicionais. Portanto, primeiro investigue se o thread em execução atual é o thread principal usando this.InvokeRequiredse retorna true, o código atual está sendo executado no thread de trabalho, então chame this.Invoke (d, new object [] {text});

caso contrário, atualize diretamente o controle da IU (aqui você tem a garantia de que está executando o código no thread principal).

Srikanth
fonte
1

Isso significa que o delegado será executado no thread de interface do usuário, mesmo se você chamar esse método de um trabalhador em segundo plano ou thread de pool de threads. Os elementos da IU têm afinidade de thread - eles só gostam de falar diretamente com uma thread: a IU thread. O thread da IU é definido como o thread que criou a instância de controle e, portanto, está associado ao identificador da janela. Mas tudo isso é um detalhe de implementação.

O ponto principal é: você chamaria esse método de um thread de trabalho para que pudesse acessar a IU (para alterar o valor em um rótulo, etc) - já que você não tem permissão para fazer isso a partir de qualquer outro thread que não seja o thread de IU.

Marc Gravell
fonte
0

Delegado são essencialmente em linha Action's ou Func<T>. Você pode declarar um delegado fora do escopo de um método que você está executando ou usando uma lambdaexpressão ( =>); porque você executa o delegado dentro de um método, você o executa no encadeamento que está sendo executado para a janela / aplicativo atual, que está em negrito.

Exemplo lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}
LukeHennerley
fonte
0

Isso significa que o delegado que você passa é executado no thread que criou o objeto Control (que é o thread de IU).

Você precisa chamar este método quando seu aplicativo é multi-threaded e você deseja fazer alguma operação de IU a partir de um thread diferente do UI thread, porque se você apenas tentar chamar um método em um Control de um thread diferente, você obterá um System.InvalidOperationException.

user1610015
fonte