Jak mohu dělat událost zpětná volání do mých win forem vlákno v bezpečí?

hlasů
33

Když se přihlásíte k události na objekt zv rámci formuláře, jste v podstatě předává kontrolu nad vaší zpětné volání metody na zdroj události. Máte tušení, zda tento zdroj událost vybrat ke spuštění události v jiném vlákně.

Problém je v tom, že když je zpětné volání vyvolána, nelze předpokládat, že si můžete udělat kontrolu aktualizací na formuláři, protože někdy tyto kontroly budou hodit spíše výjimkou, pokud je zpětné volání události byl povolán na niti jiného než závitu formulář byl spuštěn dne.

Položena 08/08/2008 v 18:32
zdroj uživatelem
V jiných jazycích...                            


6 odpovědí

hlasů
31

Pro zjednodušení Simonův kód trochu, můžete použít vestavěný v druhovém akčním delegáta. Šetří zasypával kódu s partou typů delegátů nemáte opravdu potřebují. Také v .NET 3.5 oni přidali parametr params metody Invoke takže nemusíte definovat dočasné pole.

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

   textBox1.Text = "Something happened";
}
Odpovězeno 08/08/2008 v 19:05
zdroj uživatelem

hlasů
16

Zde jsou hlavní body:

  1. Nemůžete dělat kontrolní UI hovory z různých nití, než pro který byly vytvořené na niti (ve formuláři).
  2. Delegáta vyvolání (tj, událost háčky) jsou spuštěny na stejném závitu jako objekt, který je vypalovat událost.

Takže, pokud máte samostatný „motorem“ vlákno dělá nějakou práci a mají některé UI sledování pro státní změn, které se mohou odrazit v uživatelském rozhraní (jako je pokrok bar nebo cokoliv), máte problém. Požár motoru je objekt změnil událost, který byl závislý formou. Ale callback delegát, že forma registrována s motorem volána na niti motoru ... ne na niti formuláře. A proto nelze aktualizovat všechny ovládací prvky z této zpětné volání. Doh!

BeginInvoke přichází na pomoc. Stačí použít tento jednoduchý model kódování ve všech svých zpětné volání metod a můžete si být jisti, že se věci bude v pořádku:

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

Je to opravdu docela jednoduché.

  1. Použijte InvokeRequired zjistit, jestli to zpětné volání stalo na správném nití.
  2. Pokud tomu tak není, pak reinvoke zpětné volání na správném nití se stejnými parametry. Lze reinvoke metodu pomocí Invoke (blokování) nebo BeginInvoke (neblokující) metody.
  3. Při příštím funkce se nazývá, InvokeRequired vrátí hodnotu false, protože jsme nyní na správné závitu a všichni jsou šťastní.

Jedná se o velmi kompaktní způsob, jak řešit tento problém a aby vaše formuláře bezpečných z multi-závitové zpětná volání událostí.

Odpovězeno 08/08/2008 v 18:35
zdroj uživatelem

hlasů
9

I používat anonymní metody hodně v tomto scénáři:

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}
Odpovězeno 31/08/2008 v 22:35
zdroj uživatelem

hlasů
2

Jsem trochu pozdě na toto téma, ale možná budete chtít, aby se podíval na základě událostí asynchronní vzorem . Až bude řádně provedena, to zaručuje, že události jsou vždy zvýšen z UI závitu.

Zde je krátký příklad, který umožňuje pouze jeden souběžné vyvolání; podporující více vyvolání / akce vyžaduje trochu více instalatérské práce.

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; }
        }
    }
}
Odpovězeno 04/12/2008 v 21:08
zdroj uživatelem

hlasů
1

Vzhledem k tomu lazy programmer, že mám velmi líný způsob, jak toho dosáhnout.

To, co dělám, je prostě to.

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

Dalo by se vložené do DoInvoke uvnitř funkce nebo skrýt ji v samostatném funkci dělat špinavou práci za vás.

Jen mějte na paměti, můžete projít funkce přímo do metody DoInvoke.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}
Odpovězeno 30/05/2012 v 22:57
zdroj uživatelem

hlasů
0

V mnoha jednoduchých případech, můžete použít MethodInvoker delegáta a vyhnout se, že je třeba vytvořit svůj vlastní typ delegáta.

Odpovězeno 08/08/2008 v 18:41
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more