Domanda

Quando ti iscrivi a un evento su un oggetto dall'interno di un modulo, stai essenzialmente cedendo il controllo del tuo metodo di callback all'origine dell'evento.Non hai idea se l'origine dell'evento sceglierà di attivare l'evento su un thread diverso.

Il problema è che quando viene richiamato il callback, non puoi dare per scontato di poter effettuare controlli di aggiornamento sul tuo modulo perché a volte tali controlli genereranno un'eccezione se il callback dell'evento è stato chiamato su un thread diverso da quello su cui è stato eseguito il modulo.

È stato utile?

Soluzione

Per semplificare un po' il codice di Simon, è possibile utilizzare il delegato Action generico incorporato.Ti evita di riempire il tuo codice con una serie di tipi di delegati di cui non hai realmente bisogno.Inoltre, in .NET 3.5 è stato aggiunto un parametro params al metodo Invoke in modo da non dover definire un array temporaneo.

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

   textBox1.Text = "Something happened";
}

Altri suggerimenti

Ecco i punti salienti:

  1. Non è possibile effettuare chiamate di controllo dell'interfaccia utente da un thread diverso da quello in cui sono state create (il thread del modulo).
  2. Le invocazioni dei delegati (cioè gli hook di eventi) vengono attivate sullo stesso thread dell'oggetto che sta attivando l'evento.

Quindi, se hai un thread "motore" separato che fa del lavoro e hai un'interfaccia utente che controlla i cambiamenti di stato che possono riflettersi nell'interfaccia utente (come una barra di avanzamento o altro), hai un problema.L'incendio del motore è un evento modificato dell'oggetto che è stato agganciato dal modulo.Ma il delegato di callback registrato dal modulo con il motore viene chiamato sul thread del motore... non sul thread del modulo.E quindi non puoi aggiornare alcun controllo da quel callback.Doh!

IniziaInvoca viene in soccorso.Usa semplicemente questo semplice modello di codifica in tutti i tuoi metodi di callback e puoi essere sicuro che le cose andranno bene:

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

È abbastanza semplice in realtà.

  1. Utilizzo InvokeRequired per scoprire se questa richiamata è avvenuta sul thread corretto.
  2. In caso contrario, richiamare nuovamente la richiamata sul thread corretto con gli stessi parametri.È possibile richiamare nuovamente un metodo utilizzando il comando Invocare (blocco) o IniziaInvoca metodi (non bloccanti).
  3. La prossima volta che la funzione viene chiamata, InvokeRequired restituisce false perché ora siamo sul thread corretto e tutti sono contenti.

Questo è un modo molto compatto per risolvere questo problema e proteggere i tuoi moduli dai callback di eventi multi-thread.

Utilizzo molto metodi anonimi in questo scenario:

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

Sono un po' in ritardo su questo argomento, ma potresti dare un'occhiata a Modello asincrono basato sugli eventi.Se implementato correttamente, garantisce che gli eventi vengano sempre generati dal thread dell'interfaccia utente.

Ecco un breve esempio che consente solo un'invocazione simultanea;supportare più invocazioni/eventi richiede un po' più di lavoro idraulico.

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

Come il lazy programmer, Ho un metodo molto pigro per farlo.

Quello che faccio è semplicemente questo.

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

Potresti incorporare DoInvoke all'interno della tua funzione o nasconderlo in una funzione separata per fare il lavoro sporco per te.

Tieni presente che puoi passare le funzioni direttamente nel metodo DoInvoke.

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

In molti casi semplici è possibile utilizzare il delegato MethodInvoker ed evitare la necessità di creare il proprio tipo di delegato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top