Como faço para que os retornos de chamada de eventos em meus formulários de vitória sejam seguros?

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

Pergunta

Ao assinar um evento em um objeto de dentro de um formulário, você está essencialmente transferindo o controle do seu método de retorno de chamada para a fonte do evento.Você não tem ideia se essa fonte de evento escolherá acionar o evento em um thread diferente.

O problema é que quando o retorno de chamada é invocado, você não pode assumir que pode fazer controles de atualização em seu formulário porque às vezes esses controles lançarão uma exceção se o retorno de chamada do evento tiver sido chamado em um thread diferente daquele em que o formulário foi executado.

Foi útil?

Solução

Para simplificar um pouco o código do Simon, você pode usar o delegado Action genérico integrado.Isso evita apimentar seu código com vários tipos de delegados que você realmente não precisa.Além disso, no .NET 3.5 eles adicionaram um parâmetro params ao método Invoke para que você não precise definir um array temporário.

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}

Outras dicas

Aqui estão os pontos importantes:

  1. Você não pode fazer chamadas de controle de UI de um thread diferente daquele em que foram criadas (o thread do formulário).
  2. As invocações de delegados (ou seja, ganchos de eventos) são acionadas no mesmo thread que o objeto que está disparando o evento.

Portanto, se você tiver um thread de "mecanismo" separado fazendo algum trabalho e alguma UI observando mudanças de estado que podem ser refletidas na UI (como uma barra de progresso ou qualquer outra coisa), você tem um problema.O incêndio do motor é um evento de alteração de objeto que foi fisgado pelo Formulário.Mas o delegado de retorno de chamada que o Form registrado no mecanismo é chamado no thread do mecanismo… não no thread do Form.E assim você não pode atualizar nenhum controle desse retorno de chamada.Dá!

BeginInvoke vem ao resgate.Basta usar este modelo de codificação simples em todos os seus métodos de retorno de chamada e você pode ter certeza de que tudo ficará bem:

private delegate void EventArgsDelegate(object sender, EventArgs ea);

void SomethingHappened(object sender, EventArgs ea)
{
   //
   // Make sure this callback is on the correct thread
   //
   if (this.InvokeRequired)
   {
      this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
      return;
   }

   //
   // Do something with the event such as update a control
   //
   textBox1.Text = "Something happened";
}

É muito simples, na verdade.

  1. Usar InvocarObrigatório para descobrir se esse retorno de chamada aconteceu no thread correto.
  2. Caso contrário, invoque novamente o retorno de chamada no thread correto com os mesmos parâmetros.Você pode invocar novamente um método usando o comando Invocar (bloqueio) ou BeginInvoke métodos (sem bloqueio).
  3. Na próxima vez que a função for chamada, InvocarObrigatório retorna falso porque agora estamos no tópico correto e todos estão felizes.

Esta é uma maneira muito compacta de resolver esse problema e tornar seus Formulários protegidos contra retornos de chamada de eventos multithread.

Eu uso muito métodos anônimos neste cenário:

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}

Estou um pouco atrasado para este tópico, mas você pode querer dar uma olhada no Padrão assíncrono baseado em eventos.Quando implementado corretamente, garante que os eventos sejam sempre gerados no thread da UI.

Aqui está um breve exemplo que permite apenas uma invocação simultânea;suportar múltiplas invocações/eventos requer um pouco mais de detalhamento.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class MainForm : Form
    {
        private TypeWithAsync _type;

        [STAThread()]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }

        public MainForm()
        {
            _type = new TypeWithAsync();
            _type.DoSomethingCompleted += DoSomethingCompleted;

            var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

            var btn = new Button() { Text = "Synchronous" };
            btn.Click += SyncClick;
            panel.Controls.Add(btn);

            btn = new Button { Text = "Asynchronous" };
            btn.Click += AsyncClick;
            panel.Controls.Add(btn);

            Controls.Add(panel);
        }

        private void SyncClick(object sender, EventArgs e)
        {
            int value = _type.DoSomething();
            MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
        }

        private void AsyncClick(object sender, EventArgs e)
        {
            _type.DoSomethingAsync();
        }

        private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
        {
            MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
        }
    }

    class TypeWithAsync
    {
        private AsyncOperation _operation;

        // synchronous version of method
        public int DoSomething()
        {
            Thread.Sleep(5000);
            return 27;
        }

        // async version of method
        public void DoSomethingAsync()
        {
            if (_operation != null)
            {
                throw new InvalidOperationException("An async operation is already running.");
            }

            _operation = AsyncOperationManager.CreateOperation(null);
            ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
        }

        // wrapper used by async method to call sync version of method, matches WaitCallback so it
        // can be queued by the thread pool
        private void DoSomethingAsyncCore(object state)
        {
            int returnValue = DoSomething();
            var e = new DoSomethingCompletedEventArgs(returnValue);
            _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
        }

        // wrapper used so async method can raise the event; matches SendOrPostCallback
        private void RaiseDoSomethingCompleted(object args)
        {
            OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
        }

        private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
        {
            var handler = DoSomethingCompleted;

            if (handler != null) { handler(this, e); }
        }

        public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
    }

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

        public DoSomethingCompletedEventArgs(int value)
            : base()
        {
            _value = value;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

Enquanto o lazy programmer, eu tenho um método muito preguiçoso de fazer isso.

O que eu faço é simplesmente isso.

private void DoInvoke(MethodInvoker del) {
    if (InvokeRequired) {
        Invoke(del);
    } else {
        del();
    }
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
    DoInvoke(delegate { lbl.Text = val; });
}

Você pode incorporar o DoInvoke dentro de sua função ou ocultá-lo em uma função separada para fazer o trabalho sujo para você.

Lembre-se de que você pode passar funções diretamente para o método DoInvoke.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}

Em muitos casos simples, você pode usar o delegado MethodInvoker e evitar a necessidade de criar seu próprio tipo de delegado.

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