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

C# 如何使事件回调到win窗体线程安全?,c#,.net,winforms,multithreading,events,C#,.net,Winforms,Multithreading,Events,从表单中订阅对象上的事件时,实际上是将回调方法的控制权移交给事件源。您不知道该事件源是否会选择在其他线程上触发该事件 问题是,当调用回调时,您不能假设您可以在窗体上创建更新控件,因为如果在与窗体运行线程不同的线程上调用事件回调,这些控件有时会引发异常。以下是要点: 不能从创建UI控件的线程(窗体的线程)以外的线程进行UI控件调用 委托调用(即事件挂钩)与触发事件的对象在同一线程上触发 因此,如果您有一个单独的“引擎”线程做一些工作,并且有一些UI监视可以反映在UI中的状态更改(例如进度条或其他什

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


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

以下是要点:

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

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

    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";
    }
    
    其实很简单

  • 使用invokererequired查看此回调是否发生在正确的线程上
  • 如果没有,则使用相同的参数在正确的线程上重新调用回调。您可以使用Invoke(阻塞)或BeginInvoke(非阻塞)方法重新调用方法
  • 下次调用函数时,invokererequired返回false,因为我们现在在正确的线程上,每个人都很高兴
    这是一种非常简洁的方法来解决此问题,并使表单免受多线程事件回调的影响。

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

    为了稍微简化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";
    }
    
    void somethingOccessed(对象发送方,事件参数)
    {
    如果(需要调用)
    {
    调用(新操作(发生了某些事情)、发送方、ea);
    返回;
    }
    textBox1.Text=“发生了什么事”;
    }
    
    在这种情况下,我经常使用匿名方法:

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

    关于这个话题,我有点晚了,但是你可能想看一下。当正确实现时,它保证总是从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; }
            }
        }
    }
    
    使用系统;
    使用系统组件模型;
    使用系统线程;
    使用System.Windows.Forms;
    命名空间Windows窗体应用程序1
    {
    公共类主窗体:窗体
    {
    私有类型与异步类型;
    [STAThread()]
    公共静态void Main()
    {
    Application.EnableVisualStyles();
    运行(新的MainForm());
    }
    公共表格(
    {
    _type=新类型WithAsync();
    _type.DoSomethingCompleted+=DoSomethingCompleted;
    var panel=newflowlayoutpanel(){Dock=DockStyle.Fill};
    var btn=new Button(){Text=“Synchronous”};
    点击+=同步点击;
    面板.控件.添加(btn);
    btn=新建按钮{Text=“Asynchronous”};
    点击+=异步点击;
    面板.控件.添加(btn);
    控件。添加(面板);
    }
    私有void SyncClick(对象发送方,事件参数e)
    {
    int value=_type.DoSomething();
    Show(string.Format(“DoSomething()返回{0}.”,value));
    }
    私有void异步单击(对象发送方,事件参数e)
    {
    _DoSomethingAsync()类型;
    }
    私有void DoSomethingCompleted(对象发送方,DoSomethingCompletedEventArgs e)
    {
    Show(string.Format(“DoSomethingAsync()返回{0}.”,e.Value));
    }
    }
    类TypeWithAsync
    {
    私有异步操作\u操作;
    //方法的同步版本
    公共int DoSomething()
    {
    睡眠(5000);
    返回27;
    }
    //方法的异步版本
    公共void DoSomethingAsync()
    {
    如果(_操作!=null)
    {
    抛出新的InvalidOperationException(“异步操作已在运行”);
    }
    _operation=AsyncOperationManager.CreateOperation(空);
    ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
    }
    //异步方法用于调用方法的同步版本的包装器,与WaitCallback匹配,以便
    //可以由线程池排队
    私有void DoSomethingAsyncCore(对象状态)
    {
    int returnValue=DoSomething();
    var e=新剂量计完成剂量(返回值);
    _操作。操作后完成(升起一些东西完成,e);
    }
    //用于使异步方法能够引发事件的包装器;匹配SendOrPostCallback
    私有void raisedomethingcompleted(对象参数)
    {
    OnDoSomething已完成((doSomething CompletedEventArgs)参数);
    }
    已完成的专用void ondosmething(dosomething completedeventargs e)
    {
    var handler=DoSomethingCompleted;
    if(h)
    
    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.";
    }