Cum pot face apeluri de tip eveniment în foile mele de câștig în condiții de siguranță?

Când vă abonați la un eveniment dintr-un obiect dintr-un formular, transferați în mod esențial controlul metodei de apel invers la sursa evenimentului. Nu aveți idee dacă această sursă de eveniment va alege să declanșeze evenimentul pe un fir diferit.

Problema este că atunci când se invocă inversarea apelului, nu puteți presupune că puteți face controale de actualizare a formularului, deoarece uneori acele comenzi vor arunca o expecție dacă apelul de apel al evenimentului a fost apelat pe un fir diferit de firul pe care a fost rulat formularul.

0
fr hi bn

6 răspunsuri

În multe cazuri simple, puteți utiliza delegatul MethodInvoker și evitați să creați propriul tip de delegat.

0
adăugat

Iată punctele importante:

  1. Nu puteți efectua apeluri de control UI dintr-un fir diferit de cel pe care au fost create (firul formularului).
  2. Invocațiile delegate (de exemplu, cârligele de evenimente) sunt declanșate pe același fir ca și obiectul care trage evenimentul.

Deci, dacă aveți un fir separat "motor" care face ceva lucru și are unele UI uitam de schimbări de stat care pot fi reflectate în UI (cum ar fi un bara de progres sau orice altceva), aveți o problemă. Incendiul motorului este un eveniment schimbat de obiecte care a fost atras de Formular. Dar delegatul de apel invocă faptul că Formularul înregistrat cu motorul este chemat pe firul motorului? nu pe firul Formularului. Deci, nu puteți actualiza comenzile de la acel apel invers. Doh!

BeginInvoke comes to the rescue. Just use this simple coding model in all your callback methods and you can be sure that things are going to be okay:

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

E foarte simplu.

  1. Utilizați InvokeRequired pentru a afla dacă acest apel invers a avut loc pe firul corect.
  2. Dacă nu, reinvocați apelul pe firul corect cu aceiași parametri. Puteți reinventa o metodă utilizând metodele Invocați (blocare) sau BeginInvoke (fără blocare).
  3. Data viitoare când funcția este apelată, InvokeRequired returnează false deoarece suntem acum pe firul corect și toată lumea este fericită.

Acesta este un mod foarte compact de abordare a acestei probleme și de a face ca Formularele dvs. să fie sigure de apelurile de apel cu mai multe fire.

0
adăugat
@Supercat ... reducerea evenimentului este un subiect important pentru multe aplicații, dar nu este ceva care ar trebui să facă parte din stratul UI. Trebuie să fie creată o magistrală de proxy separată pentru a recepționa, coada, combina și retrimite evenimente la intervale corespunzătoare. Orice abonat la magistrala evenimentului nu trebuie să știe că are loc o reducere a evenimentelor.
adăugat autor Simon Gillbee, sursa
Prefer general BeginInvoke pentru a invoca, dar există o avertizare: trebuie să evitați să așteptați prea multe evenimente. Folosesc o variabilă updateRequired care este setată la 1 atunci când se va întâmpla un BeginInvoke și va efectua numai BeginInvoke dacă ar fi fost zero (folosind Interlocked.Exchange). Managerul de afișare are o buclă în timp ce șterge updateRequired și, dacă nu a fost zero, face o actualizare și bucle. În unele cazuri, se adaugă un temporizator pentru a limita ulterior frecvența de actualizare (pentru a evita ca codul să își petreacă timpul în timp actualizând citirea p
adăugat autor supercat, sursa
Pot vedea locurile în care un "bus de evenimente" separat pentru a gestiona sincronizarea ar putea fi util, dar în multe cazuri ar părea cel mai ușor pentru utilizatorul final ceva de tipul unei clase de indicatori de progres dacă clasa expune pur și simplu o proprietate MinimumUpdateInterval.
adăugat autor supercat, sursa

Folosesc foarte mult metode anonime în acest scenariu:

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

Sunt cam târziu la acest subiect, dar este posibil să doriți să aruncați o privire la Modelul asincron bazat pe evenimente . Atunci când este pus în aplicare în mod corespunzător, garantează că evenimentele sunt întotdeauna ridicate din firul UI.

Iată un scurt exemplu care permite doar o invocare concurentă; susținerea mai multor invocări / evenimente necesită puțin mai multe instalații sanitare.

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

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

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

        public int Value
        {
            get { return _value; }
        }
    }
}
0
adăugat
Cred că este puțin înșelător să spui "garantează că evenimentele sunt întotdeauna ridicate din firul UI". Nu s-ar fi mai corect să spunem că se asigură că procesorul de evenimente este executat pe același SinchronizationContext / thread pe care a fost creată sarcina? (Care ar putea să nu fie fișierul UI / SynchronizationContext)
adăugat autor jspaey, sursa

Pentru a simplifica puțin codul lui Simon, ai putea folosi delegatul de acțiuni generalizat. Se salvează codul dvs. cu o grămadă de tipuri de delegați de care nu aveți nevoie. De asemenea, în .NET 3.5 au adăugat un parametru params la metoda Invoke, deci nu trebuie să definiți o matrice temporară.

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

   textBox1.Text = "Something happened";
}
0
adăugat

Ca programator lenes , am o metodă foarte leneșă de a face acest lucru.

Ceea ce fac este pur și simplu acest lucru.

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

Ai putea să introduci DoInvoke în interiorul funcției tale sau să o ascunzi în funcție separată pentru a face munca murdară pentru tine.

Rețineți că puteți trece funcții direct în metoda DoInvoke.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}
0
adăugat
Dacă folosiți .NET 3.5 sau o versiune superioară, puteți utiliza Action sau Action împreună cu expresiile lambda: Doinvoke (() => textLabel.Text = "Ceva")
adăugat autor Simon Gillbee, sursa