C#多线程值之APM二:GUI线程处理模式2

上文中讲述了工作者线程将数据同步到GUI中的第一种方式,本文讲述第二种方式:

Control.Invoke()与Control.BeginInvoke()

        // 摘要:
        //     在拥有此控件的基础窗口句柄的线程上执行指定的委托。
        //
        // 参数:
        //   method:
        //     包含要在控件的线程上下文中调用的方法的委托。
        //
        // 返回结果:
        //     正在被调用的委托的返回值,或者如果委托没有返回值,则为 null。
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public object Invoke(Delegate method);
        //
        // 摘要:
        //     在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。
        //
        // 参数:
        //   method:
        //     一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
        //
        //   args:
        //     作为指定方法的参数传递的对象数组。如果此方法没有参数,该参数可以是 null。
        //
        // 返回结果:
        //     System.Object,它包含正被调用的委托返回值;如果该委托没有返回值,则为 null。
        public object Invoke(Delegate method, params object[] args);
               //
        // 摘要:
        //     在创建控件的基础句柄所在线程上异步执行指定委托。
        //
        // 参数:
        //   method:
        //     对不带参数的方法的委托。
        //
        // 返回结果:
        //     一个表示 System.Windows.Forms.Control.BeginInvoke(System.Delegate) 操作的结果的 System.IAsyncResult。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     找不到适当的窗口句柄。
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public IAsyncResult BeginInvoke(Delegate method);
        //
        // 摘要:
        //     在创建控件的基础句柄所在线程上,用指定的参数异步执行指定委托。
        //
        // 参数:
        //   method:
        //     一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
        //
        //   args:
        //     作为给定方法的参数传递的对象数组。如果不需要参数,则可以为 null。
        //
        // 返回结果:
        //     一个表示 System.Windows.Forms.Control.BeginInvoke(System.Delegate) 操作的结果的 System.IAsyncResult。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     找不到适当的窗口句柄。
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public IAsyncResult BeginInvoke(Delegate method, params object[] args);
其中注意BeginInvoke与I/O调用中的BeginXxx的参数不一致。

BeginXxx有参数AsyncCallback作为回调函数,参数state是回调函数的实参。可以通过查询回调方法类型IAsyncResult中的只读AsyncState属性访问自己的state。CLR内部容纳了一个IAsyncResult对象引用,当BeginXxx完成后,一个线程池线程会调用回调方法,并传递内部维护的IAsyncResult的引用。因此在回调方法中可以方便的调用EndXxx。
BeginInvoke调用的回调函数不是AsyncCallback型委托因此,除非把BeginInvoke的返回值作为参数传入到回调方法中,否则很难调用EndInvoke。(不过似乎Control.EndInvoke不用调用也可以正确执行,这点还要进一步证实,目前没有结论。)

跟上节C#多线程值之APM二:GUI线程处理模式1代码基本一直,将改动代码复制如下:

private void SerialPortCallBack(IAsyncResult result)
        {
            
            try
            {
                int count = serialPort1.BaseStream.EndRead(result);

                byte[] data = (byte[])result.AsyncState;

                //Console.WriteLine(result.AsyncWaitHandle.GetHashCode());

                Console.WriteLine("\nCallBack ID: " + System.Threading.Thread.CurrentThread.GetHashCode());


                for (int i = 0; i < count; i++)
                {
                    Console.Write("{0:X2} ", data[i]);

                    if (this.richTextBox1.InvokeRequired)
                    {
                        richTextBox1.Invoke(new Action(() =>
                            {
                                richTextBox1.AppendText(string.Format("{0:X2} ", data[i]));
                                Console.WriteLine("RichTextBox Invoke ID:" + System.Threading.Thread.CurrentThread.GetHashCode());
                            }));
                    }
                    else
                    {
                        richTextBox1.AppendText(string.Format("{0:X2} ", data[i]));
                    }
                }
            }
            catch (IOException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
这里调用的是Invoke,发送端界面与上节一致,这里仅展示接收端结果界面中的控制台应用程序。


从结果中可以看出:

事件线程会变化,串口异步回调函数线程也会变化,但是RichTextBox.Invoke始终调用主界面线程。界面可快速响应鼠标操作。同时注意到与66基本在一块的显示却跑到了BB与CC之间,这明显的是由于回调线程的异步性导致的(一个是11,一个是13)两者争夺控制面板显示

我们在做一个实验,让RichTextBox.Invoke中调用Thread.Sleep(1000)

                    if (this.richTextBox1.InvokeRequired)
                    {
                        richTextBox1.Invoke(new Action(() =>
                            {
                                richTextBox1.AppendText(string.Format("{0:X2} ", data[i]));
                                Console.WriteLine("RichTextBox Invoke ID:" + System.Threading.Thread.CurrentThread.GetHashCode());
                                Thread.Sleep(1000);
                            }));
                    }
                    else。。。

此时GUI界面完全不响应,从事也堵塞了回调函数,因此回调线程ID从9一直上涨至423,非常浪费资源。

这一现象也发生在上一节中的Post中。因此为了提高用户体验,尽可能降低GUI线程的工作量。

至于BeginInvoke的调用放在下一节对比讲述。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值