Automatizando o padrão de código InvokeRequired


179

Fiquei dolorosamente ciente da frequência com que é necessário escrever o seguinte padrão de código no código da GUI orientada a eventos, em que

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

torna-se:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Esse é um padrão estranho no C #, tanto para lembrar quanto para digitar. Alguém veio com algum tipo de atalho ou construção que automatiza isso até certo ponto? Seria legal se houvesse uma maneira de anexar uma função a objetos que fazem essa verificação sem ter que passar por todo esse trabalho extra, como um object1.InvokeIfNecessary.visible = trueatalho de tipo.

Respostas anteriores discutiram a impraticabilidade de apenas chamar Invoke () todas as vezes, e mesmo assim a sintaxe Invoke () é ineficiente e ainda difícil de lidar.

Então, alguém descobriu algum atalho?


2
Eu me perguntei a mesma coisa, mas em relação ao Dispatcher.CheckAccess () do WPF.
Taylor Leese

Pensei em uma sugestão bastante louca, inspirada na sua object1.InvokeIfNecessary.Visible = truefala; confira minha resposta atualizada e deixe-me saber o que você pensa.
Dan Tao

1
Adicionar um trecho para ajudar a implementar método sugerido por Matt Davis: ver a minha resposta (tarde, mas apenas mostrando como para os leitores mais tarde ;-))
Aaron Gage

3
Não entendo por que a Microsoft não fez nada para simplificar isso no .NET. Criar representantes para cada alteração no formulário a partir do encadeamento é realmente irritante.
Kamil

@Kamil Eu não poderia concordar mais! Essa é uma supervisão, dada a sua onipresença. Dentro da estrutura, apenas lide com o encadeamento, se necessário. Parece óbvio.
21419 SteveCinq

Respostas:


138

A abordagem de Lee pode ser simplificada ainda mais

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

E pode ser chamado assim

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Não há necessidade de passar o controle como parâmetro para o delegado. C # cria automaticamente um fechamento .


ATUALIZAÇÃO :

De acordo com vários outros pôsteres, Controlpode ser generalizado como ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott apontou que, diferentemente Controlda ISynchronizeInvokeinterface, requer uma matriz de objetos para o Invokemétodo como lista de parâmetros para o action.


ATUALIZAÇÃO 2

Edições sugeridas por Mike de Klerk (veja o comentário no 1º trecho de código para inserir pontos):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Consulte o comentário do ToolmakerSteve abaixo para preocupações sobre esta sugestão.


2
Não seria melhor ter em ISynchronizeInvokevez de Control? (Parabéns a Jon Skeet stackoverflow.com/questions/711408/… )
Odys

@odyodyodys: Bom ponto. Eu não sabia ISynchronizeInvoke. Mas o único tipo que deriva dele (de acordo com Refletor) é Control, portanto, a vantagem é limitada.
Olivier Jacot-Descombes

3
@ Mike-de-caixeiro, estou preocupado com a sua sugestão para adicionar while (!control.Visible) ..sleep... Para mim, isso tem um cheiro ruim de código, pois é um atraso potencialmente ilimitado (talvez até um loop infinito em alguns casos), em código que pode ter chamadores que não esperam esse atraso (ou mesmo um impasse). IMHO, qualquer uso de Sleepdeve ser de responsabilidade de cada chamador, OU deve estar em um invólucro separado que esteja claramente marcado quanto às suas consequências. IMHO, geralmente seria melhor "falhar bastante" (exceção, capturar durante o teste) ou "não fazer nada" se o controle não estiver pronto. Comentários?
Home

1
@ OlivierJacot-Descombes, Seria ótimo se você explicasse como o thread.invokerequired funciona por trás?
Sudhir.net

1
InvokeRequiredinforma se o segmento de chamada é diferente do segmento que criou o controle. Invokepassa a ação do thread de chamada para o thread do controle em que é executado. Isso garante que, por exemplo, um manipulador de eventos de clique nunca seja interrompido.
Olivier Jacot-Descombes 28/11

133

Você pode escrever um método de extensão:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

E use-o assim:

object1.InvokeIfRequired(c => { c.Visible = true; });

EDIT: Como Simpzon aponta nos comentários, você também pode alterar a assinatura para:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Talvez eu seja burra demais, mas esse código não será compilado. Então eu corrigi-lo como ele construiu por mim (VS2008).
Oliver

5
Apenas para ser completo: no WPF, existe um mecanismo de envio diferente, mas funciona de forma análoga. Você pode usar este método de extensão: public static void InvokeIfRequired <T> (este T aTarget, Ação <T> aActionToExecute) em que T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Eu adicionei uma resposta que simplifica um pouco a solução de Lee.
Olivier Jacot-Descombes

Oi, como eu onde usando algo semelhante, pode haver um grande problema vindo desta implementação genérica. Se o controle for Disposing / Disposed, você receberá uma ObjectDisposedException.
Offler

1
@ Offler - Bem, se eles estão sendo descartados em um segmento diferente, você tem um problema de sincronização, não é um problema neste método.
Lee

33

Aqui está o formulário que eu tenho usado em todo o meu código.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Baseei isso na entrada do blog aqui . Como essa abordagem não me falhou, não vejo motivo para complicar meu código com uma verificação da InvokeRequiredpropriedade.

Espero que isto ajude.


+1 - Eu tropecei na mesma entrada de blog que você criou, e acho que essa é a abordagem mais limpa de qualquer proposta #
Tom Bushell

3
Há um pequeno impacto no desempenho usando essa abordagem, que pode se acumular quando chamado várias vezes. stackoverflow.com/a/747218/724944
surfen 29/11

4
Você precisa usar InvokeRequiredse o código puder ser executado antes do controle ser mostrado ou você terá uma exceção fatal.
56ka

9

Crie um arquivo ThreadSafeInvoke.snippet e selecione as instruções de atualização, clique com o botão direito do mouse e selecione 'Surround With ...' ou Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

Aqui está uma versão aprimorada / combinada das respostas de Lee, Oliver e Stephan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

O modelo permite um código flexível e sem conversão, que é muito mais legível enquanto o delegado dedicado fornece eficiência.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

Prefiro usar uma única instância de um método Delegate em vez de criar uma nova instância sempre. No meu caso, eu costumava mostrar mensagens de progresso e (informações / erro) de um Backroundworker copiando e lançando grandes dados de uma instância sql. Depois de cerca de 70000 chamadas de progresso e mensagens, meu formulário parou de funcionar e de mostrar novas mensagens. Isso não ocorreu quando comecei a usar um único delegado de instância global.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

Uso:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Código:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

Eu meio que gosto de fazer um pouco diferente, eu gosto de me chamar de "eu mesmo" se necessário com uma ação,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

este é um padrão útil, o IsFormClosing é um campo que eu defini como True quando estou fechando meu formulário, pois pode haver alguns threads de segundo plano que ainda estão em execução ...


-3

Você nunca deve escrever código parecido com este:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Se você possui um código parecido com este, seu aplicativo não é seguro para threads. Isso significa que você tem um código que já está chamando DoGUISwitch () de um thread diferente. É muito tarde para verificar se está em um segmento diferente. InvokeRequire deve ser chamado ANTES de fazer uma chamada para DoGUISwitch. Você não deve acessar nenhum método ou propriedade de um thread diferente.

Referência: Control.InvokeRequired Propriedade onde você pode ler o seguinte:

Além da propriedade InvokeRequired, há quatro métodos em um controle que são seguros para thread: Invoke, BeginInvoke, EndInvoke e CreateGraphics se o identificador para o controle já tiver sido criado.

Em uma arquitetura de CPU única, não há problema, mas em uma arquitetura de várias CPUs, você pode fazer com que parte do thread da UI seja atribuída ao processador em que o código de chamada estava em execução ... e se esse processador for diferente de onde o thread da UI estava em execução quando o segmento de chamada terminar, o Windows pensará que o segmento da interface do usuário terminou e interromperá o processo do aplicativo, ou seja, seu aplicativo será encerrado sem erros.


Olá, obrigado pela sua resposta. Faz anos desde que fiz essa pergunta (e quase desde que trabalhei com C #), mas fiquei pensando se você poderia explicar um pouco mais? Os documentos aos quais você vinculou se referem a um perigo específico de chamar invoke()et al antes que o controle seja tratado, mas o IMHO não descreve o que você descreveu. O objetivo de toda essa invoke()bobagem é atualizar a interface do usuário de uma maneira segura para threads, e eu acho que colocar mais instruções em um contexto de bloqueio levaria à gagueira? (Ugh ... contente eu parei de usar M $ tecnologia tão complicada.!)
Tom Corelis

Também quero ressaltar que apesar do uso frequente do código original (caminho de volta quando), eu não observar o problema que você descreveu no meu desktop dual-CPU
Tom Corelis

3
Duvido que esta resposta seja precisa, pois o MSDN mostra muitos exemplos, como o OP deu.
public wireless
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.