代码之家  ›  专栏  ›  技术社区  ›  Simon Gillbee

如何使事件回调到win窗体线程安全?

  •  33
  • Simon Gillbee  · 技术社区  · 16 年前

    从表单中订阅对象上的事件时,实际上是将回调方法的控制权移交给事件源。您不知道该事件源是否会选择在其他线程上触发该事件。

    问题是,在调用回调时,不能假定可以在窗体上创建更新控件,因为如果在与窗体运行线程不同的线程上调用事件回调,这些控件有时会引发异常。

    6 回复  |  直到 6 年前
        1
  •  35
  •   Jake Pearson    16 年前

    为了简化Simon的代码,可以使用内置的通用操作委托。这样可以避免在代码中添加一些并不需要的委托类型。此外,在.NET3.5中,他们向Invoke方法添加了一个params参数,因此您不必定义临时数组。

    void SomethingHappened(object sender, EventArgs ea)
    {
       if (InvokeRequired)
       {
          Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
          return;
       }
    
       textBox1.Text = "Something happened";
    }
    
        2
  •  18
  •   Simon Gillbee    16 年前

    以下是要点:

    1. 不能从创建UI控件的线程(窗体的线程)以外的线程进行UI控件调用。
    2. 委托调用(即事件挂钩)与触发事件的对象在同一线程上触发。

    因此,如果您有一个单独的“引擎”线程做一些工作,并且有一些UI监视可以反映在UI中的状态更改(例如进度条或其他什么),那么您就有问题了。引擎火灾是一个对象更改事件,已被窗体钩住。但是向引擎注册的窗体在引擎的线程上而不是在窗体的线程上被调用的回调委托。因此,您无法从该回调更新任何控件。啊!

    异步调用 来营救。只要在所有回调方法中使用这个简单的编码模型,就可以确保一切都会好起来:

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

    其实很简单。

    1. 使用
    2. 如果没有,则使用相同的参数在正确的线程上重新调用回调。您可以使用 援引 (阻塞)或 (非阻塞)方法。
    3. 下次调用函数时, 调用所需 返回false,因为我们现在在正确的线程上,每个人都很高兴。

        3
  •  9
  •   Jason Diller    16 年前

    在这种情况下,我经常使用匿名方法:

    void SomethingHappened(object sender, EventArgs ea)
    {
       MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
       InvokeRequired ? Invoke( del ) : del(); 
    }
    
        4
  •  2
  •   OwenP    16 年前

    这个话题我有点晚了,但是你可能想看看 Event-Based Asynchronous Pattern . 当正确实现时,它保证总是从UI线程引发事件。

    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; }
            }
        }
    }
    
        5
  •  2
  •   Chase    12 年前

    作为 lazy programmer ,我有一个非常懒惰的方法来做这件事。

    我所做的就是这样。

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

    private void directPass() {
        DoInvoke(this.directInvoke);
    }
    private void directInvoke() {
        textLabel.Text = "Directly passed.";
    }
    
        6
  •  0
  •   Chris Farmer Marcelo Cantos    16 年前

    在许多简单的情况下,您可以使用MethodInvoker委托,而无需创建自己的委托类型。