É apropriado para estender o controle para fornecer funcionalidade Invoke / BeginInvoke consistentemente seguro?

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

Pergunta

No decorrer da minha manutenção para um aplicativo mais antigo que mal violado as regras de atualização cross-fio em winforms, eu criei o seguinte método de extensão como uma maneira de corrigir rapidamente as chamadas ilegais quando eu descobri-los:

/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater.  False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread.  If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {
        if (!uiElement.IsHandleCreated)
        {
            // Do nothing if the handle isn't created already.  The user's responsible
            // for ensuring that the handle they give us exists.
            return;
        }

        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Exemplo de uso:

this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);

Eu gosto de como eu posso alavancar fechamentos de ler, também, embora forceSynchronous precisa ser verdadeira nesse caso:

string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);

Eu não questionam a utilidade deste método para a fixação de chamadas ilegais no código legado, mas que sobre novo código?

É bom projeto para usar esse método para atualização UI em um pedaço de um novo software quando você pode não saber o segmento está tentando atualizar a interface do usuário, ou deveria novo código Winforms geralmente contêm um específico, método dedicado com o Invoke() apropriada encanamento -relacionados para todas essas atualizações de interface do usuário? (Vou tentar usar as outras técnicas de processamento de fundo apropriadas em primeiro lugar, é claro, por exemplo BackgroundWorker.)

Curiosamente isso não vai funcionar para ToolStripItems . Eu só descobri recentemente que decorrem directamente da Component vez da partir de Controlo . Em vez disso, deve ser usado invocação do ToolStrip contendo.

Acompanhamento aos comentários:

Alguns comentários sugerem que:

if (uiElement.InvokeRequired)

deve ser:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

Considere o seguinte href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx" rel="noreferrer"> documentação :

Isto significa que InvokeRequired lata false retorno se Invoke não é necessária (A chamada ocorre no mesmo segmento), ou se o controle foi criado em um thread diferente, mas do controle alça ainda não foi criado.

No caso em que o identificador do controle ainda não foi criado, você deve não simplesmente chamar propriedades, métodos, ou eventos de controle. este poder causa o identificador do controle a ser criado na discussão de fundo, isolando o controlo de um segmento sem uma bomba de mensagem e fazer o aplicação instável.

Você pode proteger contra este caso a também verificar o valor de IsHandleCreated quando InvokeRequired retorna falso em uma discussão de fundo.

Se o controle foi criado em um segmento diferente, mas o identificador do controle ainda não foi criado, InvokeRequired retorna false. Isto significa que se InvokeRequired retornos true, IsHandleCreated será sempre verdade. Testá-lo novamente é redundante e incorreta.

Outras dicas

Você deve criar Begin e métodos de extensão End também. E se você usar os genéricos, você pode fazer a chamada olhar um pouco mais agradável.

public static class ControlExtensions
{
  public static void InvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    if (@this.InvokeRequired)
    {
      @this.Invoke(action, new object[] { @this });
    }
    else
    {
      if (!@this.IsHandleCreated)
        return;
      if (@this.IsDisposed)
        throw new ObjectDisposedException("@this is disposed.");

      action(@this);
    }
  }

  public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); });
  }

  public static void EndInvokeEx<T>(this T @this, IAsyncResult result)
    where T : Control
  {
    @this.EndInvoke(result);
  }
}

Agora suas chamadas obter um aspirador de pouco mais curto e:

this.lblTimeDisplay.InvokeEx(l => l.Text = this.task.Duration.ToString());

var result = this.BeginInvokeEx(f => f.Text = "Different Title");
// ... wait
this.EndInvokeEx(result);

E com relação a Components, apenas invoco sobre a forma ou o próprio recipiente.

this.InvokeEx(f => f.toolStripItem1.Text = "Hello World");

Esta não é realmente uma resposta, mas responde a alguns comentários para a resposta aceita.

Para padrões IAsyncResult padrão, o método BeginXXX contém parâmetro AsyncCallback, por isso, se você quer dizer "Eu não me importo com isso - apenas chamar EndInvoke quando ele é feito e ignorar o resultado", você pode fazer algo como isto ( isto é para Action mas deve ser capaz de ser ajustado para outros tipos de delegado):

    ...
    public static void BeginInvokeEx(this Action a){
        a.BeginInvoke(a.EndInvoke, a);
    }
    ...
    // Don't worry about EndInvoke
    // it will be called when finish
    new Action(() => {}).BeginInvokeEx(); 

(Infelizmente eu não tenho uma solução para não ter uma função auxiliar sem declarar uma variável de cada vez que o uso desse padrão).

Mas para Control.BeginInvoke não temos AsyncCallBack, então não há nenhuma maneira fácil de expressar isso com Control.EndInvoke garantido para ser chamado. A forma como ele foi concebido prompts o fato de que Control.EndInvoke é opcional.

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