首页 / 知识

关于c#:如何使事件回调进入Win窗体线程安全?

2023-04-11 21:56:00

关于c#:如何使事件回调进入Win窗体线程安全?

How do I make event callbacks into my win forms thread safe?

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

问题在于,调用回调时,您不能假定可以在表单上创建更新控件,因为有时如果在与运行表单的线程不同的线程上调用事件回调,则这些控件将引发异常开启。


要稍微简化Simon的代码,可以使用内置的通用Action委托。它节省了您不需要的大量委托类型,从而使您的代码更加实用。此外,在.NET 3.5中,他们向Invoke方法添加了params参数,因此您不必定义临时数组。

1
2
3
4
5
6
7
8
9
10
void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text ="Something happened";
}

以下是要点:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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";
    }

    真的很简单。

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


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

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

    作为lazy programmer,我有一个非常懒惰的方法。

    我要做的就是这个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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; });
    }

    您可以在函数中内联DoInvoke或将其隐藏在单独的函数中,以为您做脏工作。

    请记住,您可以将函数直接传递到DoInvoke方法中。

    1
    2
    3
    4
    5
    6
    private void directPass() {
        DoInvoke(this.directInvoke);
    }
    private void directInvoke() {
        textLabel.Text ="Directly passed.";
    }

    我对这个话题有些迟了,但是您可能想看看基于事件的异步模式。正确实施后,可以确保始终从UI线程引发事件。

    这是一个简短的示例,仅允许一个并发调用;支持多个调用/事件需要更多的工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    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; }
            }
        }
    }

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


    事件回调线程安全窗体

    最新内容

    相关内容

    热门文章

    推荐文章

    标签云

    猜你喜欢