Frage

Wenn Sie ein Ereignis für ein Objekt innerhalb eines Formulars abonnieren, übergeben Sie im Wesentlichen die Kontrolle über Ihre Rückrufmethode an die Ereignisquelle.Sie haben keine Ahnung, ob diese Ereignisquelle das Ereignis in einem anderen Thread auslösen wird.

Das Problem besteht darin, dass Sie beim Aufruf des Rückrufs nicht davon ausgehen können, dass Sie Aktualisierungssteuerelemente für Ihr Formular erstellen können, da diese Steuerelemente manchmal eine Ausnahme auslösen, wenn der Ereignisrückruf in einem anderen Thread als dem Thread aufgerufen wurde, in dem das Formular ausgeführt wurde.

War es hilfreich?

Lösung

Um Simons Code etwas zu vereinfachen, können Sie den integrierten generischen Action-Delegaten verwenden.Es erspart Ihnen, Ihren Code mit einer Reihe von Delegatentypen zu überhäufen, die Sie nicht wirklich benötigen.Außerdem wurde in .NET 3.5 der Invoke-Methode ein params-Parameter hinzugefügt, sodass Sie kein temporäres Array definieren müssen.

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

   textBox1.Text = "Something happened";
}

Andere Tipps

Hier sind die wichtigsten Punkte:

  1. Sie können UI-Steueraufrufe nicht von einem anderen Thread als dem ausführen, in dem sie erstellt wurden (dem Thread des Formulars).
  2. Delegatenaufrufe (d. h. Ereignis-Hooks) werden auf demselben Thread ausgelöst wie das Objekt, das das Ereignis auslöst.

Wenn Sie also einen separaten „Engine“-Thread haben, der einige Arbeiten ausführt und eine Benutzeroberfläche auf Statusänderungen überwacht, die sich in der Benutzeroberfläche widerspiegeln können (z. B. einen Fortschrittsbalken oder was auch immer), haben Sie ein Problem.Der Motorbrand ist ein Objektänderungsereignis, das durch das Formular eingebunden wurde.Aber der Rückrufdelegierte, den das bei der Engine registrierte Formular angibt, wird im Thread der Engine aufgerufen … nicht im Thread des Formulars.Daher können Sie über diesen Rückruf keine Steuerelemente aktualisieren.Doh!

BeginInvoke kommt zur Rettung.Verwenden Sie einfach dieses einfache Codierungsmodell in allen Ihren Rückrufmethoden und Sie können sicher sein, dass alles gut wird:

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

Es ist eigentlich ganz einfach.

  1. Verwenden InvokeRequired um herauszufinden, ob dieser Rückruf im richtigen Thread stattgefunden hat.
  2. Wenn nicht, rufen Sie den Rückruf im richtigen Thread mit denselben Parametern erneut auf.Sie können eine Methode erneut aufrufen, indem Sie die verwenden Aufrufen (Blockierung) oder BeginInvoke (nicht blockierende) Methoden.
  3. Beim nächsten Aufruf der Funktion InvokeRequired gibt false zurück, da wir uns jetzt im richtigen Thread befinden und alle zufrieden sind.

Dies ist eine sehr kompakte Möglichkeit, dieses Problem anzugehen und Ihre Formulare vor Multithread-Ereignisrückrufen zu schützen.

In diesem Szenario verwende ich häufig anonyme Methoden:

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

Ich komme mit diesem Thema etwas spät dran, aber vielleicht möchten Sie einen Blick darauf werfen Ereignisbasiertes asynchrones Muster.Bei ordnungsgemäßer Implementierung wird garantiert, dass Ereignisse immer vom UI-Thread ausgelöst werden.

Hier ist ein kurzes Beispiel, das nur einen gleichzeitigen Aufruf zulässt.Die Unterstützung mehrerer Aufrufe/Ereignisse erfordert etwas mehr Aufwand.

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

Als die lazy programmer, ich habe eine sehr faule Methode, dies zu tun.

Was ich mache, ist einfach Folgendes.

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

Sie können DoInvoke in Ihre Funktion integrieren oder in einer separaten Funktion verstecken, um die Drecksarbeit für Sie zu erledigen.

Denken Sie daran, dass Sie Funktionen direkt an die DoInvoke-Methode übergeben können.

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

In vielen einfachen Fällen können Sie den MethodInvoker-Delegaten verwenden und die Notwendigkeit vermeiden, einen eigenen Delegatentyp zu erstellen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top